Skip to content

Commit

Permalink
refactor(repeater): simplify FromDictionary method
Browse files Browse the repository at this point in the history
  • Loading branch information
derevnjuk committed Jun 8, 2024
1 parent fb0478d commit 23c8dde
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 127 deletions.
95 changes: 37 additions & 58 deletions src/SecTester.Repeater/Bus/IncomingRequest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Serialization;
using MessagePack;
using SecTester.Repeater.Internal;
using SecTester.Repeater.Runners;
Expand All @@ -20,75 +17,57 @@ public record IncomingRequest(Uri Url) : IRequest
private const string BodyKey = "body";
private const string ProtocolKey = "protocol";

private static readonly Dictionary<string, Protocol> ProtocolEntries = typeof(Protocol)
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(field => new
{
Value = (Protocol)field.GetValue(null),
StringValue = field.GetCustomAttribute<EnumMemberAttribute>()?.Value ?? field.Name
})
.ToDictionary(x => MessagePackNamingPolicy.SnakeCase.ConvertName(x.StringValue), x => x.Value);

[Key(ProtocolKey)]
public Protocol Protocol { get; set; } = Protocol.Http;

private IEnumerable<KeyValuePair<string, IEnumerable<string>>> _headers = Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>();
[Key(ProtocolKey)] public Protocol Protocol { get; set; } = Protocol.Http;

[Key(HeadersKey)]
public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Headers
{
get => _headers;
// ADHOC: convert from a kind of assignable type to formatter resolvable type
set => _headers = value.AsEnumerable();
}
[Key(HeadersKey)] public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Headers { get; set; } = new Dictionary<string, IEnumerable<string>>();

[Key(BodyKey)]
public string? Body { get; set; }
[Key(BodyKey)] public string? Body { get; set; }

[Key(MethodKey)]
public HttpMethod Method { get; set; } = HttpMethod.Get;
[Key(MethodKey)] public HttpMethod Method { get; set; } = HttpMethod.Get;

[Key(UrlKey)]
public Uri Url { get; set; } = Url ?? throw new ArgumentNullException(nameof(Url));
[Key(UrlKey)] public Uri Url { get; set; } = Url ?? throw new ArgumentNullException(nameof(Url));

public static IncomingRequest FromDictionary(Dictionary<object, object> dictionary)
{
var protocol = !dictionary.ContainsKey(ProtocolKey) || (dictionary.TryGetValue(ProtocolKey, out var p1) && p1 is null)
? Protocol.Http
: dictionary.TryGetValue(ProtocolKey, out var p2) && p2 is string && ProtocolEntries.TryGetValue(p2.ToString(), out var e)
? e
: throw new InvalidDataException(FormatPropertyError(ProtocolKey));

var uri = dictionary.TryGetValue(UrlKey, out var u) && u is string
? new Uri(u.ToString())
: throw new InvalidDataException(FormatPropertyError(UrlKey));

var method = dictionary.TryGetValue(MethodKey, out var m) && m is string
? new HttpMethod(m.ToString())
var protocol = dictionary.TryGetValue(ProtocolKey, out var protocolObj) && protocolObj is string protocolStr
? (Protocol)Enum.Parse(typeof(Protocol), protocolStr, true)
: Protocol.Http;

var headers = dictionary.TryGetValue(HeadersKey, out var headersObj) && headersObj is Dictionary<object, object> headersDict
? ConvertToHeaders(headersDict)
: new Dictionary<string, IEnumerable<string>>();

var body = dictionary.TryGetValue(BodyKey, out var bodyObj)
? bodyObj?.ToString()
: null;

var method = dictionary.TryGetValue(MethodKey, out var methodObj) && methodObj is string methodStr
? HttpMethods.Items.TryGetValue(methodStr, out var m) && m is not null
? m
: HttpMethod.Get
: HttpMethod.Get;

var body = dictionary.TryGetValue(BodyKey, out var b) && b is string ? b.ToString() : null;

var headers = dictionary.TryGetValue(HeadersKey, out var h) && h is Dictionary<object, object> value
? MapHeaders(value)
: Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>();
var url = dictionary.TryGetValue(UrlKey, out var urlObj) && urlObj is string urlStr
? new Uri(urlStr)
: null;

return new IncomingRequest(uri)
return new IncomingRequest(url!)
{
Protocol = protocol,
Headers = headers,
Body = body,
Method = method,
Headers = headers
Method = method
};
}

private static IEnumerable<KeyValuePair<string, IEnumerable<string>>> MapHeaders(Dictionary<object, object> headers) =>
headers.Select(kvp => kvp.Value switch
{
IEnumerable<object> strings => new KeyValuePair<string, IEnumerable<string>>(kvp.Key.ToString(), strings.Select(x => x.ToString())),
null => new KeyValuePair<string, IEnumerable<string>>(kvp.Key.ToString(), Enumerable.Empty<string>()),
_ => new KeyValuePair<string, IEnumerable<string>>(kvp.Key.ToString(), new[] { kvp.Value.ToString() })
});

private static string FormatPropertyError(string propName) => $"{propName} is either null or has an invalid data type or value";
private static IEnumerable<KeyValuePair<string, IEnumerable<string>>> ConvertToHeaders(Dictionary<object, object> headers) =>
headers.ToDictionary(
kvp => kvp.Key.ToString()!,
kvp => kvp.Value switch
{
IEnumerable<object> list => list.Select(v => v.ToString()!),
string str => new[] { str },
_ => Enumerable.Empty<string>()
}
);
}
10 changes: 1 addition & 9 deletions src/SecTester.Repeater/Bus/OutgoingResponse.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using MessagePack;
using SecTester.Repeater.Runners;

Expand All @@ -23,13 +22,6 @@ public record OutgoingResponse : IResponse
[Key("errorCode")]
public string? ErrorCode { get; set; }

private IEnumerable<KeyValuePair<string, IEnumerable<string>>> _headers = Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>();

[Key("headers")]
public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Headers
{
get => _headers;
// ADHOC: convert from a kind of assignable type to formatter resolvable type
set => _headers = value.AsEnumerable();
}
public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Headers { get; set; } = new Dictionary<string, IEnumerable<string>>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace SecTester.Repeater.Internal;

internal class DefaultMessagePackSerializerOptions
internal static class DefaultMessagePackSerializerOptions
{
internal static readonly MessagePackSerializerOptions Instance = new(
CompositeResolver.Create(
Expand Down
30 changes: 30 additions & 0 deletions src/SecTester.Repeater/Internal/HttpMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;

namespace SecTester.Repeater.Internal;

public class HttpMethods
{
public static IDictionary<string, HttpMethod> Items { get; } = typeof(HttpMethod)
.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(x => x.PropertyType.IsAssignableFrom(typeof(HttpMethod)))
.Select(x => x.GetValue(null))
.Cast<HttpMethod>()
.Concat(new List<HttpMethod>
{
new("PATCH"),
new("COPY"),
new("LINK"),
new("UNLINK"),
new("PURGE"),
new("LOCK"),
new("UNLOCK"),
new("PROPFIND"),
new("VIEW")
})
.Distinct()
.ToDictionary(x => x.Method, x => x, StringComparer.InvariantCultureIgnoreCase);
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using MessagePack;
using MessagePack.Formatters;

namespace SecTester.Repeater.Internal;

internal class MessagePackHttpMethodFormatter : IMessagePackFormatter<HttpMethod?>
{
private static readonly IEnumerable<HttpMethod> BaseMethods = typeof(HttpMethod)
.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(x => x.PropertyType.IsAssignableFrom(typeof(HttpMethod)))
.Select(x => x.GetValue(null))
.Cast<HttpMethod>();

private static readonly IEnumerable<HttpMethod> CustomMethods = new List<HttpMethod>
{
new("PATCH"),
new("COPY"),
new("LINK"),
new("UNLINK"),
new("PURGE"),
new("LOCK"),
new("UNLOCK"),
new("PROPFIND"),
new("VIEW")
};

private static readonly IDictionary<string, HttpMethod> Methods = BaseMethods.Concat(CustomMethods).Distinct()
.ToDictionary(x => x.Method, x => x, StringComparer.InvariantCultureIgnoreCase);
public void Serialize(ref MessagePackWriter writer, HttpMethod? value, MessagePackSerializerOptions options)
{
if (null == value)
Expand Down Expand Up @@ -63,7 +38,7 @@ public void Serialize(ref MessagePackWriter writer, HttpMethod? value, MessagePa
{
var token = reader.ReadString();

if (token is null || !Methods.TryGetValue(token, out var method))
if (token is null || !HttpMethods.Items.TryGetValue(token, out var method))
{
throw new MessagePackSerializationException(
$"Unexpected value {token} when parsing the {nameof(HttpMethod)}.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ internal class MessagePackStringEnumMemberFormatter<T> : IMessagePackFormatter<T
})
.ToDictionary(x => x.Value, x => x.StringValue);

private readonly Dictionary<string, T> CasedStringToEnum;
private readonly Dictionary<T, string> CasedEnumToString;
private readonly Dictionary<string, T> _casedStringToEnum;
private readonly Dictionary<T, string> _casedEnumToString;

public MessagePackStringEnumMemberFormatter(MessagePackNamingPolicy namingPolicy)
{
this.CasedEnumToString = EnumToString.ToDictionary(x => x.Key, x => namingPolicy.ConvertName(x.Value));
this.CasedStringToEnum = EnumToString.ToDictionary(x => namingPolicy.ConvertName(x.Value), x => x.Key);
this._casedEnumToString = EnumToString.ToDictionary(x => x.Key, x => namingPolicy.ConvertName(x.Value));
this._casedStringToEnum = EnumToString.ToDictionary(x => namingPolicy.ConvertName(x.Value), x => x.Key);
}

public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options)
{
if (!CasedEnumToString.TryGetValue(value, out var stringValue))
if (!_casedEnumToString.TryGetValue(value, out var stringValue))
{
throw new MessagePackSerializationException($"No string representation found for {value}");
}
Expand All @@ -43,7 +43,7 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions
{
var stringValue = reader.ReadString();

if (null == stringValue || !CasedStringToEnum.TryGetValue(stringValue, out var enumValue))
if (null == stringValue || !_casedStringToEnum.TryGetValue(stringValue, out var enumValue))
{
throw new MessagePackSerializationException($"Unable to parse '{stringValue}' to {typeof(T).Name}.");
}
Expand Down
2 changes: 2 additions & 0 deletions src/SecTester.Repeater/SecTester.Repeater.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

<ItemGroup>
<Folder Include="Api"/>
<Folder Include="Bus" />
<Folder Include="Extensions"/>
<Folder Include="Internal"/>
<Folder Include="Runners"/>
</ItemGroup>

Expand Down
24 changes: 2 additions & 22 deletions test/SecTester.Repeater.Tests/Bus/IncomingRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void IncomingRequest_FromDictionary_ShouldCreateInstance(IncomingRequest
}

[Fact]
public void IncomingRequest_FromDictionary_ShouldThrowWhenRequiredPropertyWasNotProvided()
public void IncomingRequest_FromDictionary_ShouldThrowWhenProtocolIsInvalid()
{
// arrange
var packJson =
Expand All @@ -72,7 +72,7 @@ public void IncomingRequest_FromDictionary_ShouldThrowWhenRequiredPropertyWasNot
var act = () => IncomingRequest.FromDictionary(deserializedDictionary);

// assert
act.Should().Throw<InvalidDataException>();
act.Should().Throw<ArgumentException>();
}

[Fact]
Expand Down Expand Up @@ -114,24 +114,4 @@ public void IncomingRequest_FromDictionary_ShouldParseProtocolValue()
// assert
result.Should().BeEquivalentTo(new IncomingRequest(new Uri("https://foo.bar/1")) { Protocol = Protocol.Http });
}

[Fact]
public void IncomingRequest_FromDictionary_ShouldThrowWhenProtocolIsNotParsable()
{
// arrange
var packJson =
"{\"type\":2,\"data\":[\"request\",{\"protocol\":\"ws\",\"url\":\"https://foo.bar/1\"}],\"options\":{\"compress\":true},\"id\":1,\"nsp\":\"/some\"}";

var serializer = new SocketIOMessagePackSerializer(Options);

var deserializedPackMessage = MessagePackSerializer.Deserialize<PackMessage>(MessagePackSerializer.ConvertFromJson(packJson), Options);

var deserializedDictionary = serializer.Deserialize<Dictionary<object, object>>(deserializedPackMessage, 1);

// act
var act = () => IncomingRequest.FromDictionary(deserializedDictionary);

// assert
act.Should().Throw<InvalidDataException>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,23 @@ public sealed class MessagePackHttpHeadersFormatterTests
)
);

public static readonly IEnumerable<object[]> Fixtures = new List<object[]>()
public static readonly IEnumerable<object?[]> Fixtures = new List<object?[]>()
{
new object[]
new object?[]
{
null
},
new object[]
{
Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>()

},
new object[]
new object?[]
{
new List<KeyValuePair<string, IEnumerable<string>>>
{
new("content-type", new List<string> { "application/json" }),
new("cache-control", new List<string> { "no-cache", "no-store" })
}.AsEnumerable()
}
}
};

Expand Down

0 comments on commit 23c8dde

Please sign in to comment.