Skip to content

Commit f09d020

Browse files
Make converters injectable and respect culture
1 parent b4b3fd5 commit f09d020

27 files changed

+479
-315
lines changed

src/ImageSharp.Web/Commands/CommandDescriptor.cs

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/ImageSharp.Web/Commands/CommandParser.cs

Lines changed: 30 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Globalization;
7+
using System.Linq;
78
using System.Net;
9+
using System.Runtime.CompilerServices;
810
using SixLabors.ImageSharp.Web.Commands.Converters;
911

1012
namespace SixLabors.ImageSharp.Web.Commands
@@ -14,44 +16,18 @@ namespace SixLabors.ImageSharp.Web.Commands
1416
/// </summary>
1517
public sealed class CommandParser
1618
{
17-
/// <summary>
18-
/// A new instance of the <see cref="CommandParser"/> class with lazy initialization.
19-
/// </summary>
20-
private static readonly Lazy<CommandParser> Lazy = new Lazy<CommandParser>(() => new CommandParser());
19+
private readonly ICommandConverter[] converters;
2120

2221
/// <summary>
23-
/// Prevents a default instance of the <see cref="CommandParser"/> class from being created.
22+
/// Initializes a new instance of the <see cref="CommandParser"/> class.
2423
/// </summary>
25-
private CommandParser()
24+
/// <param name="converters">The collection of command converters.</param>
25+
public CommandParser(IEnumerable<ICommandConverter> converters)
2626
{
27-
this.AddSimpleConverters();
28-
this.AddListConverters();
29-
this.AddArrayConverters();
30-
this.AddColorConverters();
27+
Guard.NotNull(converters, nameof(converters));
28+
this.converters = converters.ToArray();
3129
}
3230

33-
/// <summary>
34-
/// Gets the current <see cref="CommandParser"/> instance.
35-
/// </summary>
36-
public static CommandParser Instance => Lazy.Value;
37-
38-
/// <summary>
39-
/// Adds a command converter to the parser.
40-
/// </summary>
41-
/// <param name="type">The <see cref="Type"/> to add a converter for. </param>
42-
/// <param name="converterType">The type of <see cref="CommandConverter"/> to add.</param>
43-
public void AddConverter(Type type, Type converterType) => CommandDescriptor.AddConverter(type, converterType);
44-
45-
/// <summary>
46-
/// Parses the given string value converting it to the given using the invariant culture.
47-
/// </summary>
48-
/// <param name="value">The string value to parse.</param>
49-
/// <typeparam name="T">
50-
/// The <see cref="Type"/> to convert the string to.
51-
/// </typeparam>
52-
/// <returns>The converted instance or the default.</returns>
53-
public T ParseValue<T>(string value) => this.ParseValue<T>(value, CultureInfo.InvariantCulture);
54-
5531
/// <summary>
5632
/// Parses the given string value converting it to the given type.
5733
/// </summary>
@@ -61,99 +37,38 @@ private CommandParser()
6137
/// The <see cref="Type"/> to convert the string to.
6238
/// </typeparam>
6339
/// <returns>The converted instance or the default.</returns>
40+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6441
public T ParseValue<T>(string value, CultureInfo culture)
65-
{
66-
culture ??= CultureInfo.InvariantCulture;
67-
68-
Type type = typeof(T);
69-
ICommandConverter converter = CommandDescriptor.GetConverter(type);
70-
try
71-
{
72-
return (T)converter.ConvertFrom(culture, WebUtility.UrlDecode(value), type);
73-
}
74-
catch
75-
{
76-
return default;
77-
}
78-
}
79-
80-
/// <summary>
81-
/// Add the generic converters.
82-
/// </summary>
83-
private void AddSimpleConverters()
84-
{
85-
this.AddConverter(TypeConstants.Sbyte, typeof(IntegralNumberConverter<sbyte>));
86-
this.AddConverter(TypeConstants.Byte, typeof(IntegralNumberConverter<byte>));
87-
88-
this.AddConverter(TypeConstants.Short, typeof(IntegralNumberConverter<short>));
89-
this.AddConverter(TypeConstants.UShort, typeof(IntegralNumberConverter<ushort>));
90-
91-
this.AddConverter(TypeConstants.Int, typeof(IntegralNumberConverter<int>));
92-
this.AddConverter(TypeConstants.UInt, typeof(IntegralNumberConverter<uint>));
93-
94-
this.AddConverter(TypeConstants.Long, typeof(IntegralNumberConverter<long>));
95-
this.AddConverter(TypeConstants.ULong, typeof(IntegralNumberConverter<ulong>));
96-
97-
this.AddConverter(typeof(decimal), typeof(SimpleCommandConverter<decimal>));
98-
this.AddConverter(typeof(float), typeof(SimpleCommandConverter<float>));
99-
100-
this.AddConverter(typeof(double), typeof(SimpleCommandConverter<double>));
101-
this.AddConverter(typeof(string), typeof(SimpleCommandConverter<string>));
102-
103-
this.AddConverter(typeof(bool), typeof(SimpleCommandConverter<bool>));
104-
}
105-
106-
/// <summary>
107-
/// Adds a selection of default list type converters.
108-
/// </summary>
109-
private void AddListConverters()
110-
{
111-
this.AddConverter(typeof(List<sbyte>), typeof(ListConverter<sbyte>));
112-
this.AddConverter(typeof(List<byte>), typeof(ListConverter<byte>));
113-
114-
this.AddConverter(typeof(List<short>), typeof(ListConverter<short>));
115-
this.AddConverter(typeof(List<ushort>), typeof(ListConverter<ushort>));
116-
117-
this.AddConverter(typeof(List<int>), typeof(ListConverter<int>));
118-
this.AddConverter(typeof(List<uint>), typeof(ListConverter<uint>));
119-
120-
this.AddConverter(typeof(List<long>), typeof(ListConverter<long>));
121-
this.AddConverter(typeof(List<ulong>), typeof(ListConverter<ulong>));
122-
123-
this.AddConverter(typeof(List<decimal>), typeof(ListConverter<decimal>));
124-
this.AddConverter(typeof(List<float>), typeof(ListConverter<float>));
125-
this.AddConverter(typeof(List<double>), typeof(ListConverter<double>));
126-
127-
this.AddConverter(typeof(List<string>), typeof(ListConverter<string>));
128-
}
42+
=> (T)this.ParseValue(typeof(T), value, culture);
12943

13044
/// <summary>
131-
/// Adds a selection of default array type converters.
45+
/// Parses the given string value converting it to the given type.
13246
/// </summary>
133-
private void AddArrayConverters()
47+
/// <param name="type"> The type to convert the string to.</param>
48+
/// <param name="value">The string value to parse.</param>
49+
/// <param name="culture">The <see cref="CultureInfo"/> to use as the current culture.</param>
50+
/// <returns>The converted instance or the default value.</returns>
51+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
52+
public object ParseValue(Type type, string value, CultureInfo culture)
13453
{
135-
this.AddConverter(typeof(sbyte[]), typeof(ArrayConverter<sbyte>));
136-
this.AddConverter(typeof(byte[]), typeof(ArrayConverter<byte>));
137-
138-
this.AddConverter(typeof(short[]), typeof(ArrayConverter<short>));
139-
this.AddConverter(typeof(ushort[]), typeof(ArrayConverter<ushort>));
54+
DebugGuard.NotNull(type, nameof(type));
55+
DebugGuard.NotNull(culture, nameof(culture));
14056

141-
this.AddConverter(typeof(int[]), typeof(ArrayConverter<int>));
142-
this.AddConverter(typeof(uint[]), typeof(ArrayConverter<uint>));
57+
// This allows us to reuse the same converter for infinite enum types.
58+
Type matchType = type.IsEnum ? typeof(Enum) : type;
14359

144-
this.AddConverter(typeof(long[]), typeof(ArrayConverter<long>));
145-
this.AddConverter(typeof(ulong[]), typeof(ArrayConverter<ulong>));
60+
ICommandConverter converter = Array.Find(this.converters, x => x.Type.Equals(matchType));
14661

147-
this.AddConverter(typeof(decimal[]), typeof(ArrayConverter<decimal>));
148-
this.AddConverter(typeof(float[]), typeof(ArrayConverter<float>));
149-
this.AddConverter(typeof(double[]), typeof(ArrayConverter<double>));
62+
if (converter is null)
63+
{
64+
ThrowNotSupported(type);
65+
}
15066

151-
this.AddConverter(typeof(string[]), typeof(ArrayConverter<string>));
67+
return converter.ConvertFrom(this, culture, WebUtility.UrlDecode(value), type);
15268
}
15369

154-
/// <summary>
155-
/// Adds the default color converters.
156-
/// </summary>
157-
private void AddColorConverters() => this.AddConverter(TypeConstants.Color, typeof(ColorConverter));
70+
[MethodImpl(MethodImplOptions.NoInlining)]
71+
private static void ThrowNotSupported(Type type)
72+
=> throw new NotSupportedException($"Cannot convert to {type.FullName}.");
15873
}
15974
}

src/ImageSharp.Web/Commands/Converters/ArrayConverter{T}.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ namespace SixLabors.ImageSharp.Web.Commands.Converters
1414
internal sealed class ArrayConverter<T> : ListConverter<T>
1515
{
1616
/// <inheritdoc/>
17-
public override object ConvertFrom(CultureInfo culture, string value, Type propertyType)
18-
=> ((List<T>)base.ConvertFrom(culture, value, propertyType)).ToArray();
17+
public override Type Type => typeof(T[]);
18+
19+
/// <inheritdoc/>
20+
public override object ConvertFrom(
21+
CommandParser parser,
22+
CultureInfo culture,
23+
string value,
24+
Type propertyType)
25+
=> ((List<T>)base.ConvertFrom(parser, culture, value, propertyType)).ToArray();
1926
}
2027
}

src/ImageSharp.Web/Commands/Converters/ColorConverter.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Web.Commands.Converters
1212
/// <summary>
1313
/// Allows the conversion of strings into rgba32 pixel colors.
1414
/// </summary>
15-
internal class ColorConverter : CommandConverter
15+
internal class ColorConverter : ICommandConverter
1616
{
1717
/// <summary>
1818
/// The web color hexadecimal regex. Matches strings arranged
@@ -31,7 +31,10 @@ internal class ColorConverter : CommandConverter
3131
private static readonly Lazy<IDictionary<string, Color>> ColorConstantsTable = new Lazy<IDictionary<string, Color>>(InitializeColorConstantsTable);
3232

3333
/// <inheritdoc/>
34-
public override object ConvertFrom(CultureInfo culture, string value, Type propertyType)
34+
public Type Type => typeof(Color);
35+
36+
/// <inheritdoc/>
37+
public object ConvertFrom(CommandParser parser, CultureInfo culture, string value, Type propertyType)
3538
{
3639
if (string.IsNullOrWhiteSpace(value))
3740
{
@@ -56,11 +59,14 @@ public override object ConvertFrom(CultureInfo culture, string value, Type prope
5659

5760
if (convert)
5861
{
59-
List<byte> rgba = CommandParser.Instance.ParseValue<List<byte>>(value);
62+
List<byte> rgba = parser.ParseValue<List<byte>>(value, culture);
6063

61-
return rgba.Count == 4
62-
? Color.FromRgba(rgba[0], rgba[1], rgba[2], rgba[3])
63-
: Color.FromRgb(rgba[0], rgba[1], rgba[2]);
64+
return rgba.Count switch
65+
{
66+
4 => Color.FromRgba(rgba[0], rgba[1], rgba[2], rgba[3]),
67+
3 => Color.FromRgb(rgba[0], rgba[1], rgba[2]),
68+
_ => default,
69+
};
6470
}
6571
}
6672

@@ -72,7 +78,7 @@ public override object ConvertFrom(CultureInfo culture, string value, Type prope
7278

7379
// Named colors
7480
IDictionary<string, Color> table = ColorConstantsTable.Value;
75-
return table.ContainsKey(value) ? table[value] : base.ConvertFrom(culture, value, propertyType);
81+
return table.ContainsKey(value) ? table[value] : default;
7682
}
7783

7884
/// <summary>
@@ -82,9 +88,10 @@ public override object ConvertFrom(CultureInfo culture, string value, Type prope
8288
private static IDictionary<string, Color> InitializeColorConstantsTable()
8389
{
8490
IDictionary<string, Color> table = new Dictionary<string, Color>(StringComparer.OrdinalIgnoreCase);
85-
foreach (FieldInfo field in TypeConstants.Color.GetFields(BindingFlags.Public | BindingFlags.Static))
91+
92+
foreach (FieldInfo field in typeof(Color).GetFields(BindingFlags.Public | BindingFlags.Static))
8693
{
87-
if (field.FieldType == TypeConstants.Color)
94+
if (field.FieldType == typeof(Color))
8895
{
8996
table[field.Name] = (Color)field.GetValue(null);
9097
}

src/ImageSharp.Web/Commands/Converters/CommandConverter.cs

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)