Skip to content

Commit

Permalink
Merge pull request #11 from pamidur/feature/enum-parsing
Browse files Browse the repository at this point in the history
Made type-converter enum aware
  • Loading branch information
KyryloLehonkov authored Feb 12, 2020
2 parents f73b08e + f8dfaa3 commit 872b241
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 23 deletions.
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"files.exclude": {
"**/bin": true,
"**/obj": true
},
"search.exclude": {
"**/obj": true
}
}
18 changes: 10 additions & 8 deletions src/QRest.Core/Compilation/TypeConverters/DefaultTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ public bool TryConvert(Expression expression, Type target, out Expression result
result = null;
var key = (expression.Type, target);

if (_converters.TryGetValue(key, out var converter))
result = converter(expression, _format);
else if(expression.Type == target)
result = expression;
else if (target.IsAssignableFrom(expression.Type))
result = Expression.Convert(expression, target);
if (_converters.TryGetValue(key, out var converter))
result = converter(expression, _format);
else if (expression.Type == target)
result = expression;
else if (target.IsAssignableFrom(expression.Type))
result = Expression.Convert(expression, target);
else if (target.IsEnum && target.GetEnumUnderlyingType().IsAssignableFrom(expression.Type))
result = Expression.Convert(expression, target);
else if (expression.Type == typeof(string) && _parseStrings)
{
var parser = StringParser.GetParser(target);
Expand All @@ -40,8 +42,8 @@ public bool TryConvert(Expression expression, Type target, out Expression result
_converters[key] = parser;
result = parser(expression, _format);
}
}
}

return result != null;
}

Expand Down
41 changes: 26 additions & 15 deletions src/QRest.Core/Compilation/TypeConverters/StringParser.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace QRest.Core.Compilation.TypeConverters
{
public class StringParser
{
private static readonly string _parseMethodName = "Parse";
private static readonly string _parseMethodName = "Parse";
private static readonly Type[] _parseWithFormatSignature = new[] { typeof(string), typeof(IFormatProvider) };
private static readonly Type[] _parseSignature = new[] { typeof(string) };
private static readonly MethodInfo _enumParser = typeof(Enum).GetMethod(nameof(Enum.Parse), new[] { typeof(Type), typeof(string) });

private static readonly Dictionary<Type, Func<Expression, IFormatProvider, Expression>> _parsers = new Dictionary<Type, Func<Expression, IFormatProvider, Expression>>();

public static Func<Expression, IFormatProvider, Expression> GetParser(Type type)
{
if (!_parsers.ContainsKey(type))
{
var source = Expression.Parameter(typeof(string));

Func<Expression, IFormatProvider, Expression[]> parameters = null;

var method = type.GetMethod(_parseMethodName, _parseWithFormatSignature);
if (method != null) parameters = (e, c) => new Expression[] { e, Expression.Constant(c, typeof(IFormatProvider)) };
else
if (type.IsEnum)
{
method = type.GetMethod(_parseMethodName, _parseSignature);
if (method != null)
parameters = (e, c) => new Expression[] { e };
_parsers[type] = ParseEnum(type);
}

if (method != null && parameters != null)
_parsers[type] = (e, c) => Expression.Call(method, parameters(e, c));
else
_parsers[type] = null;
{
_parsers[type] = ParseOtherTypes(type) ?? throw new CompilationException($"Cannot find parser for type {type}");
}
}

return _parsers[type];
}

private static Func<Expression, IFormatProvider, Expression> ParseOtherTypes(Type type)
{
var method = type.GetMethod(_parseMethodName, _parseWithFormatSignature);
if (method != null)
return (e, c) => Expression.Call(method, new[] { e, Expression.Constant(c, typeof(IFormatProvider)) });

method = type.GetMethod(_parseMethodName, _parseSignature);
if (method != null)
return (e, c) => Expression.Call(method, new[] { e });

return null;
}

private static Func<Expression, IFormatProvider, Expression> ParseEnum(Type type)
{
return (e, c) => Expression.Convert(Expression.Call(_enumParser, new[] { Expression.Constant(type), e }), type);
}
}
}
67 changes: 67 additions & 0 deletions test/QRest.Compiler.Standard.Tests/StringParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using QRest.Core.Compilation.TypeConverters;
using System;
using System.Globalization;
using System.Linq.Expressions;
using Xunit;

namespace QRest.Compiler.Standard.Tests
{
public enum SimpleEnum
{
First,
Second
}

public enum IntEnum : int
{
First = 10,
Second = 20
}


public class StringParserTests
{
private readonly DefaultTypeConverter _converter;

public StringParserTests()
{
_converter = new DefaultTypeConverter(CultureInfo.InvariantCulture, true);
}

[Fact(DisplayName = "Can parse string enum")]
public void Can_Parse_String_Enum()
{
var param = Expression.Parameter(typeof(string));
Assert.True(_converter.TryConvert(param, typeof(SimpleEnum), out var parser));

var lambda = Expression.Lambda<Func<string, SimpleEnum>>(parser, param);
var result = lambda.Compile()(SimpleEnum.Second.ToString());

Assert.Equal(SimpleEnum.Second, result);
}

[Fact(DisplayName = "Can parse int enum")]
public void Can_Parse_Int_Enum()
{
var param = Expression.Parameter(typeof(int));
Assert.True(_converter.TryConvert(param, typeof(IntEnum), out var parser));

var lambda = Expression.Lambda<Func<int, IntEnum>>(parser, param);
var result = lambda.Compile()((int)IntEnum.Second);

Assert.Equal(IntEnum.Second, result);
}

[Fact(DisplayName = "Can parse stringly-int enum")]
public void Can_Parse_Stringly_Int_Enum()
{
var param = Expression.Parameter(typeof(string));
Assert.True(_converter.TryConvert(param, typeof(IntEnum), out var parser));

var lambda = Expression.Lambda<Func<string, IntEnum>>(parser, param);
var result = lambda.Compile()(((int)IntEnum.Second).ToString());

Assert.Equal(IntEnum.Second, result);
}
}
}

0 comments on commit 872b241

Please sign in to comment.