From f8dfaa3c8d07c5ca62b2d065947d7a44e593041f Mon Sep 17 00:00:00 2001 From: Oleksandr Hulyi Date: Wed, 12 Feb 2020 14:04:59 +0100 Subject: [PATCH] Made type-converter enum aware --- .vscode/launch.json | 11 +++ .vscode/settings.json | 9 +++ .../TypeConverters/DefaultTypeConverter.cs | 18 ++--- .../TypeConverters/StringParser.cs | 41 +++++++----- .../StringParserTests.cs | 67 +++++++++++++++++++ 5 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 test/QRest.Compiler.Standard.Tests/StringParserTests.cs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b26b008 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7c4fa65 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.exclude": { + "**/bin": true, + "**/obj": true + }, + "search.exclude": { + "**/obj": true + } +} \ No newline at end of file diff --git a/src/QRest.Core/Compilation/TypeConverters/DefaultTypeConverter.cs b/src/QRest.Core/Compilation/TypeConverters/DefaultTypeConverter.cs index a0d1304..4bba165 100644 --- a/src/QRest.Core/Compilation/TypeConverters/DefaultTypeConverter.cs +++ b/src/QRest.Core/Compilation/TypeConverters/DefaultTypeConverter.cs @@ -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); @@ -40,8 +42,8 @@ public bool TryConvert(Expression expression, Type target, out Expression result _converters[key] = parser; result = parser(expression, _format); } - } - + } + return result != null; } diff --git a/src/QRest.Core/Compilation/TypeConverters/StringParser.cs b/src/QRest.Core/Compilation/TypeConverters/StringParser.cs index 34fc8d6..704abd2 100644 --- a/src/QRest.Core/Compilation/TypeConverters/StringParser.cs +++ b/src/QRest.Core/Compilation/TypeConverters/StringParser.cs @@ -1,14 +1,16 @@ 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> _parsers = new Dictionary>(); @@ -16,26 +18,35 @@ public static Func GetParser(Type type) { if (!_parsers.ContainsKey(type)) { - var source = Expression.Parameter(typeof(string)); - - Func 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 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 ParseEnum(Type type) + { + return (e, c) => Expression.Convert(Expression.Call(_enumParser, new[] { Expression.Constant(type), e }), type); + } } } diff --git a/test/QRest.Compiler.Standard.Tests/StringParserTests.cs b/test/QRest.Compiler.Standard.Tests/StringParserTests.cs new file mode 100644 index 0000000..853db16 --- /dev/null +++ b/test/QRest.Compiler.Standard.Tests/StringParserTests.cs @@ -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>(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>(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>(parser, param); + var result = lambda.Compile()(((int)IntEnum.Second).ToString()); + + Assert.Equal(IntEnum.Second, result); + } + } +} \ No newline at end of file