Skip to content

Commit d552162

Browse files
committed
Make Http request/response formatter public
1 parent 4afaab7 commit d552162

File tree

56 files changed

+194
-112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+194
-112
lines changed

FluentAssertions.Web.sln

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Api.v8.Tests", "test
5252
EndProject
5353
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentAssertions.Web.v8.Tests", "test\FluentAssertions.Web.v8.Tests\FluentAssertions.Web.v8.Tests.csproj", "{056E517C-F644-4B8A-9494-09276E6E3C6B}"
5454
EndProject
55-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentAssertions.Web.Types", "src\FluentAssertions.Web.Types\FluentAssertions.Web.Types.csproj", "{455E5F41-B678-4286-BB4D-FC232D08CAC0}"
55+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ited.HttpFormatter", "src\Ited.HttpFormatter\Ited.HttpFormatter.csproj", "{8C66744D-AC2B-4086-AC2C-5BD04D1BC0D3}"
56+
EndProject
57+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ited.HttpFormatter.Tests", "test\Ited.HttpFormatter.Tests\Ited.HttpFormatter.Tests.csproj", "{6BA3687F-995A-4726-96D6-366F277384F1}"
5658
EndProject
5759
Global
5860
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -104,10 +106,14 @@ Global
104106
{056E517C-F644-4B8A-9494-09276E6E3C6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
105107
{056E517C-F644-4B8A-9494-09276E6E3C6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
106108
{056E517C-F644-4B8A-9494-09276E6E3C6B}.Release|Any CPU.Build.0 = Release|Any CPU
107-
{455E5F41-B678-4286-BB4D-FC232D08CAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
108-
{455E5F41-B678-4286-BB4D-FC232D08CAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
109-
{455E5F41-B678-4286-BB4D-FC232D08CAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
110-
{455E5F41-B678-4286-BB4D-FC232D08CAC0}.Release|Any CPU.Build.0 = Release|Any CPU
109+
{8C66744D-AC2B-4086-AC2C-5BD04D1BC0D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
110+
{8C66744D-AC2B-4086-AC2C-5BD04D1BC0D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
111+
{8C66744D-AC2B-4086-AC2C-5BD04D1BC0D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
112+
{8C66744D-AC2B-4086-AC2C-5BD04D1BC0D3}.Release|Any CPU.Build.0 = Release|Any CPU
113+
{6BA3687F-995A-4726-96D6-366F277384F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
114+
{6BA3687F-995A-4726-96D6-366F277384F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
115+
{6BA3687F-995A-4726-96D6-366F277384F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
116+
{6BA3687F-995A-4726-96D6-366F277384F1}.Release|Any CPU.Build.0 = Release|Any CPU
111117
EndGlobalSection
112118
GlobalSection(SolutionProperties) = preSolution
113119
HideSolutionNode = FALSE
@@ -125,7 +131,8 @@ Global
125131
{3872DF64-5DA9-4983-90D8-2802A0A26CFE} = {14FDD52D-B83A-445B-BD2F-04B3E7B8033C}
126132
{EBB6D06F-B87F-4BDA-8F1B-A6E88C96C2E1} = {BFF7517A-C9EA-458D-829E-28A10F8D61BF}
127133
{056E517C-F644-4B8A-9494-09276E6E3C6B} = {BFF7517A-C9EA-458D-829E-28A10F8D61BF}
128-
{455E5F41-B678-4286-BB4D-FC232D08CAC0} = {14FDD52D-B83A-445B-BD2F-04B3E7B8033C}
134+
{8C66744D-AC2B-4086-AC2C-5BD04D1BC0D3} = {14FDD52D-B83A-445B-BD2F-04B3E7B8033C}
135+
{6BA3687F-995A-4726-96D6-366F277384F1} = {BFF7517A-C9EA-458D-829E-28A10F8D61BF}
129136
EndGlobalSection
130137
GlobalSection(ExtensibilityGlobals) = postSolution
131138
SolutionGuid = {81F41C75-2F8A-4E70-BA17-38146C4BB6E6}

src/FluentAssertions.Web.Serializers.NewtonsoftJson/FluentAssertions.Web.Serializers.NewtonsoftJson.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<PackageDescription>
@@ -11,7 +11,7 @@
1111
</ItemGroup>
1212

1313
<ItemGroup>
14-
<ProjectReference Include="..\FluentAssertions.Web.Types\FluentAssertions.Web.Types.csproj" />
14+
<ProjectReference Include="..\FluentAssertions.Web\FluentAssertions.Web.csproj" />
1515
</ItemGroup>
1616

1717
</Project>

src/FluentAssertions.Web.Serializers.NewtonsoftJson/GlobalUsings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
global using Newtonsoft.Json;
1+
global using Ited.HttpFormatter;
2+
global using Newtonsoft.Json;
23
global using System;
34
global using System.IO;
45
global using System.Text;

src/FluentAssertions.Web.Serializers.NewtonsoftJson/NewtonsoftJsonSerializer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// ReSharper disable once CheckNamespace
2+
23
namespace FluentAssertions;
34

45
/// <summary>

src/FluentAssertions.Web.v8/FluentAssertions.Web.v8.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</ItemGroup>
1919

2020
<ItemGroup>
21-
<ProjectReference Include="..\FluentAssertions.Web.Types\FluentAssertions.Web.Types.csproj" />
21+
<ProjectReference Include="..\Ited.HttpFormatter\Ited.HttpFormatter.csproj" />
2222
</ItemGroup>
2323

2424
<ItemGroup>

src/FluentAssertions.Web/FluentAssertions.Web.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</ItemGroup>
1313

1414
<ItemGroup>
15-
<ProjectReference Include="..\FluentAssertions.Web.Types\FluentAssertions.Web.Types.csproj" />
15+
<ProjectReference Include="..\Ited.HttpFormatter\Ited.HttpFormatter.csproj" />
1616
</ItemGroup>
1717

1818
</Project>

src/FluentAssertions.Web.Types/FluentAssertionsWebConfig.cs renamed to src/FluentAssertions.Web/FluentAssertionsWebConfig.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// ReSharper disable once CheckNamespace
2-
namespace FluentAssertions;
1+
namespace FluentAssertions;
32

43
/// <summary>
54
/// Holder of the global <see cref="FluentAssertionsWebConfig"/>

src/FluentAssertions.Web/GlobalUsings.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
global using FluentAssertions.Execution;
33
global using FluentAssertions.Web;
44
global using FluentAssertions.Web.Internal;
5-
global using FluentAssertions.Web.Internal.Serializers;
5+
global using Ited.HttpFormatter;
66
global using System;
77
global using System.Collections.Generic;
88
global using System.Diagnostics;
9-
global using System.IO;
109
global using System.Linq;
1110
global using System.Net;
1211
global using System.Net.Http;
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using FluentAssertions.Formatting;
2-
using System.Text;
32

43
namespace FluentAssertions.Web.Internal;
54

@@ -14,15 +13,8 @@ public void Format(object value,
1413
{
1514
var assertionsFailures = (AssertionsFailures)value;
1615

17-
var messageBuilder = new StringBuilder();
18-
messageBuilder.AppendLine();
19-
messageBuilder.AppendLine();
16+
var formatted = AssertionsFailuresFormatted.GetFormatted(assertionsFailures);
2017

21-
foreach (var failure in assertionsFailures.FailuresMessages)
22-
{
23-
messageBuilder.AppendLine($" - { failure.ReplaceFirstWithLowercase() }");
24-
}
25-
26-
formattedGraph.AddFragment(messageBuilder.ToString());
18+
formattedGraph.AddFragment(formatted);
2719
}
2820
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using FluentAssertions.Formatting;
2+
3+
internal class HttpResponseMessageFormatter : IValueFormatter
4+
{
5+
public bool CanHandle(object value) => value is HttpResponseMessage;
6+
7+
/// <inheritdoc />
8+
public void Format(object value,
9+
FormattedObjectGraph formattedGraph,
10+
FormattingContext context,
11+
FormatChild formatChild)
12+
{
13+
var response = (HttpResponseMessage)value;
14+
15+
var formatted = HttpResponseMessageFormatted.GetFormatted(response);
16+
17+
formattedGraph.AddFragment(formatted);
18+
}
19+
}

src/FluentAssertions.Web/Internal/AssertionsFailures.cs renamed to src/Ited.HttpFormatter/AssertionsFailures.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
namespace FluentAssertions.Web.Internal;
1+
namespace Ited.HttpFormatter;
22

3-
internal class AssertionsFailures
3+
public class AssertionsFailures
44
{
55
public AssertionsFailures(string[] failuresMessages)
66
{
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Text;
2+
3+
namespace Ited.HttpFormatter;
4+
5+
public class AssertionsFailuresFormatted
6+
{
7+
public static string GetFormatted(AssertionsFailures assertionsFailures)
8+
{
9+
var messageBuilder = new StringBuilder();
10+
messageBuilder.AppendLine();
11+
messageBuilder.AppendLine();
12+
13+
foreach (var failure in assertionsFailures.FailuresMessages)
14+
{
15+
messageBuilder.AppendLine($" - {failure.ReplaceFirstWithLowercase()}");
16+
}
17+
return messageBuilder.ToString();
18+
}
19+
}

src/FluentAssertions.Web/Internal/ContentFormatterOptions.cs renamed to src/Ited.HttpFormatter/ContentFormatterOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
namespace FluentAssertions.Web.Internal;
1+
namespace Ited.HttpFormatter;
22

3-
internal static class ContentFormatterOptions
3+
public static class ContentFormatterOptions
44
{
55
public const int MaximumReadableBytes = 128 * 1024; // 1KB holds like 500 words
66
public const string WarningMessageWhenDisposed = "***** Content is disposed so it cannot be read. *****";

src/FluentAssertions.Web.Types/DeserializationException.cs renamed to src/Ited.HttpFormatter/DeserializationException.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// ReSharper disable once CheckNamespace
2-
using System;
32
using System.Runtime.Serialization;
43

5-
namespace FluentAssertions;
4+
namespace Ited.HttpFormatter;
65

76
/// <summary>
87
/// Captures serialization exceptions.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
global using Ited.HttpFormatter.Internal;
2+
global using Ited.HttpFormatter;
3+
global using System;
4+
global using System.Collections.Generic;
5+
global using System.IO;
6+
global using System.Linq;
7+
global using System.Net;
8+
global using System.Net.Http;
9+
global using System.Text.Json;
10+
global using System.Threading.Tasks;

src/FluentAssertions.Web/Internal/Guard.cs renamed to src/Ited.HttpFormatter/Guard.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
namespace FluentAssertions.Web.Internal;
1+
namespace Ited.HttpFormatter;
22

3-
internal static class Guard
3+
public static class Guard
44
{
55
public static void ThrowIfArgumentIsNull<T>(T obj, string paramName)
66
where T : class

src/FluentAssertions.Web/Internal/HttpContentExtensions.cs renamed to src/Ited.HttpFormatter/HttpContentExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal;
3+
namespace Ited.HttpFormatter;
44

5-
internal static class HttpContentExtensions
5+
public static class HttpContentExtensions
66
{
77
public static async Task<T?> ReadAsAsync<T>(this HttpContent content, ISerializer serializer)
88
{
9-
var model = await ReadAsAsync(content, typeof(T), serializer);
9+
var model = await content.ReadAsAsync(typeof(T), serializer);
1010
return (T?)model;
1111
}
1212

@@ -19,7 +19,7 @@ internal static class HttpContentExtensions
1919
return result;
2020
}
2121

22-
public static Task<T?> ReadAsAsync<T>(this HttpContent content, T _, ISerializer serializer)
22+
public static Task<T?> ReadAsAsync<T>(this HttpContent content, T _, ISerializer serializer)
2323
=> content.ReadAsAsync<T>(serializer);
2424

2525
public static bool IsDisposed(this HttpContent? content)

src/FluentAssertions.Web/Internal/HttpResponseMessageExtensions.cs renamed to src/Ited.HttpFormatter/HttpResponseMessageExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
namespace FluentAssertions.Web.Internal;
1+
namespace Ited.HttpFormatter;
22

3-
internal static class HttpResponseMessageExtensions
3+
public static class HttpResponseMessageExtensions
44
{
55
public static IEnumerable<string> GetHeaderValues(this HttpResponseMessage response, string header)
66
{

src/FluentAssertions.Web/HttpResponseMessageFormatter.cs renamed to src/Ited.HttpFormatter/HttpResponseMessageFormatted.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
1-
using FluentAssertions.Formatting;
2-
using FluentAssertions.Web.Internal.ContentProcessors;
1+
using Ited.HttpFormatter.Internal.ContentProcessors;
32
using System.Text;
43

5-
namespace FluentAssertions.Web;
4+
namespace Ited.HttpFormatter;
65

7-
internal class HttpResponseMessageFormatter : IValueFormatter
6+
public class HttpResponseMessageFormatted
87
{
9-
public bool CanHandle(object value) => value is HttpResponseMessage;
10-
11-
/// <inheritdoc />
12-
public void Format(object value,
13-
FormattedObjectGraph formattedGraph,
14-
FormattingContext context,
15-
FormatChild formatChild)
8+
public static string GetFormatted(HttpResponseMessage response)
169
{
17-
var response = (HttpResponseMessage)value;
18-
1910
var messageBuilder = new StringBuilder();
2011
messageBuilder.AppendLine();
2112
messageBuilder.AppendLine();
@@ -24,7 +15,8 @@ public void Format(object value,
2415
Func<Task> contentResolver = async () => await AppendHttpResponseMessage(messageBuilder, response);
2516
contentResolver.ExecuteInDefaultSynchronizationContext().GetAwaiter().GetResult();
2617

27-
formattedGraph.AddFragment(messageBuilder.ToString());
18+
var formatted = messageBuilder.ToString();
19+
return formatted;
2820
}
2921

3022
private static async Task AppendHttpResponseMessage(StringBuilder messageBuilder, HttpResponseMessage response)
@@ -115,4 +107,4 @@ private static void AppendContentLength(StringBuilder messageBuilder, HttpConten
115107
}
116108
}
117109
}
118-
}
110+
}

src/FluentAssertions.Web.Types/ISerializer.cs renamed to src/Ited.HttpFormatter/ISerializer.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
// ReSharper disable once CheckNamespace
2-
using System;
3-
using System.IO;
4-
using System.Threading.Tasks;
5-
6-
namespace FluentAssertions;
1+
namespace Ited.HttpFormatter;
72

83
/// <summary>
94
/// Provides an abstraction to deserialize a Stream of binary data into a C# object.

src/FluentAssertions.Web/Internal/ContentProcessors/Appender.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/Appender.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal static class Appender
66
{

src/FluentAssertions.Web/Internal/ContentProcessors/BinaryProcessor.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/BinaryProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal class BinaryProcessor : ProcessorBase
66
{

src/FluentAssertions.Web/Internal/ContentProcessors/FallbackProcessor.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/FallbackProcessor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal class FallbackProcessor : ProcessorBase
66
{
@@ -28,7 +28,7 @@ protected override async Task Handle(StringBuilder contentBuilder)
2828
}
2929

3030
// we might get here some StreamContent, let's try to print it
31-
// but let's try not to get into this issue again https://github.com/adrianiftode/FluentAssertions.Web/issues/93
31+
// but let's try not to get into this issue again https://github.com/adrianiftode/Ited.HttpFormatter/issues/93
3232
var content = await _httpContent!.SafeReadAsStringAsync();
3333
AppendContentWithinLimits(contentBuilder, content);
3434
}

src/FluentAssertions.Web/Internal/ContentProcessors/IContentProcessor.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/IContentProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal interface IContentProcessor
66
{

src/FluentAssertions.Web/Internal/ContentProcessors/InternalServerErrorProcessor.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/InternalServerErrorProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal class InternalServerErrorProcessor : ProcessorBase
66
{

src/FluentAssertions.Web/Internal/ContentProcessors/JsonProcessor.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/JsonProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Text.Encodings.Web;
33
using System.Text.Unicode;
44

5-
namespace FluentAssertions.Web.Internal.ContentProcessors;
5+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
66

77
internal class JsonProcessor : ProcessorBase
88
{

src/FluentAssertions.Web/Internal/ContentProcessors/MultipartProcessor.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/MultipartProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal class MultipartProcessor : ProcessorBase
66
{

src/FluentAssertions.Web/Internal/ContentProcessors/ProcessorBase.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/ProcessorBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal abstract class ProcessorBase : IContentProcessor
66
{

src/FluentAssertions.Web/Internal/ContentProcessors/ProcessorsRunner.cs renamed to src/Ited.HttpFormatter/Internal/ContentProcessors/ProcessorsRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text;
22

3-
namespace FluentAssertions.Web.Internal.ContentProcessors;
3+
namespace Ited.HttpFormatter.Internal.ContentProcessors;
44

55
internal static class ProcessorsRunner
66
{

src/FluentAssertions.Web/Internal/NoSynchronizationContextScope.cs renamed to src/Ited.HttpFormatter/Internal/NoSynchronizationContextScope.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Threading;
22

3-
namespace FluentAssertions.Web.Internal;
3+
namespace Ited.HttpFormatter.Internal;
44

55
internal static class NoSynchronizationContextScope
66
{

0 commit comments

Comments
 (0)