Skip to content

Commit 2bc2d67

Browse files
committed
UnitConverter: Throw custom exceptions if types or enum values not found
Add QuantityNotFoundException Add UnitNotFoundException Add test cases for invalid quantity and unit input
1 parent b496269 commit 2bc2d67

File tree

4 files changed

+185
-6
lines changed

4 files changed

+185
-6
lines changed

UnitsNet.Tests/UnitConverterTest.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ public void ConvertByName_ConvertsTheValueToGivenUnit(double expectedValue, doub
3535
Assert.Equal(expectedValue, UnitConverter.ConvertByName(inputValue, quantityTypeName, fromUnit, toUnit));
3636
}
3737

38+
[Theory]
39+
[InlineData(1, "UnknownQuantity", "Meter", "Centimeter")]
40+
public void ConvertByName_ThrowsQuantityNotFoundExceptionOnUnknownQuantity(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
41+
{
42+
Assert.Throws<QuantityNotFoundException>(() => UnitConverter.ConvertByName(inputValue, quantityTypeName, fromUnit, toUnit));
43+
}
44+
45+
[Theory]
46+
[InlineData(1, "Length", "UnknownFromUnit", "Centimeter")]
47+
[InlineData(1, "Length", "Meter", "UnknownToUnit")]
48+
public void ConvertByName_ThrowsUnitNotFoundExceptionOnUnknownFromOrToUnit(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
49+
{
50+
Assert.Throws<UnitNotFoundException>(() => UnitConverter.ConvertByName(inputValue, quantityTypeName, fromUnit, toUnit));
51+
}
52+
3853
[Theory]
3954
[InlineData(1, "UnknownQuantity", "Meter", "Centimeter")]
4055
[InlineData(1, "Length", "UnknownFromUnit", "Centimeter")]
@@ -67,6 +82,21 @@ public void ConvertByAbbreviation_ConvertsTheValueToGivenUnit(double expectedVal
6782
Assert.Equal(expectedValue, UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
6883
}
6984

85+
[Theory]
86+
[InlineData(1, "UnknownQuantity", "m", "cm")]
87+
public void ConvertByAbbreviation_ThrowsQuantityNotFoundExceptionOnUnknownQuantity(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
88+
{
89+
Assert.Throws<QuantityNotFoundException>(() => UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
90+
}
91+
92+
[Theory]
93+
[InlineData(1, "Length", "UnknownFromUnit", "cm")]
94+
[InlineData(1, "Length", "m", "UnknownToUnit")]
95+
public void ConvertByAbbreviation_ThrowsUnitNotFoundExceptionOnUnknownFromOrToUnitAbbreviation(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
96+
{
97+
Assert.Throws<UnitNotFoundException>(() => UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
98+
}
99+
70100
[Theory]
71101
[InlineData(1, "UnknownQuantity", "m", "cm")]
72102
[InlineData(1, "Length", "UnknownFromUnit", "cm")]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright © 2007 Andreas Gullberg Larsen ([email protected]).
2+
// https://github.com/anjdreas/UnitsNet
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
using System;
23+
24+
namespace UnitsNet
25+
{
26+
/// <summary>
27+
/// Quantity type was not found. This is typically thrown for dynamic conversions,
28+
/// such as <see cref="UnitConverter.ConvertByName" />.
29+
/// </summary>
30+
#if WINDOWS_UWP
31+
internal
32+
#else
33+
public
34+
#endif
35+
class QuantityNotFoundException : UnitsNetException
36+
{
37+
public QuantityNotFoundException()
38+
{
39+
}
40+
41+
public QuantityNotFoundException(string message) : base(message)
42+
{
43+
}
44+
45+
public QuantityNotFoundException(string message, Exception innerException) : base(message, innerException)
46+
{
47+
}
48+
}
49+
}

UnitsNet/UnitConverter.cs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,15 @@ public static class UnitConverter
6969
/// </param>
7070
/// <example>double centimeters = ConvertByName(5, "Length", "Meter", "Centimeter"); // 500</example>
7171
/// <returns>Output value as the result of converting to <paramref name="toUnit" />.</returns>
72+
/// <exception cref="UnitNotFoundException">No units match the abbreviation.</exception>
73+
/// <exception cref="AmbiguousUnitParseException">More than one unit matches the abbrevation.</exception>
7274
public static double ConvertByName(double fromValue, string quantityName, string fromUnit, string toUnit)
7375
{
74-
Type quantityType = UnitsNetAssembly.GetType($"{QuantityNamespace}.{quantityName}"); // ex: UnitsNet.Length struct
75-
Type unitType = UnitsNetAssembly.GetType($"{UnitTypeNamespace}.{quantityName}Unit"); // ex: UnitsNet.Units.LengthUnit enum
76+
Type quantityType = GetQuantityType(quantityName);
77+
Type unitType = GetUnitType(quantityName);
7678

77-
object fromUnitValue = Enum.Parse(unitType, fromUnit); // ex: LengthUnit.Meter
78-
object toUnitValue = Enum.Parse(unitType, toUnit); // ex: LengthUnit.Centimeter
79+
object fromUnitValue = ParseUnit(unitType, fromUnit); // ex: LengthUnit.Meter
80+
object toUnitValue = ParseUnit(unitType, toUnit); // ex: LengthUnit.Centimeter
7981

8082
MethodInfo fromMethod = GetStaticFromMethod(quantityType, unitType); // ex: UnitsNet.Length.From(double inputValue, LengthUnit inputUnit)
8183
object fromResult = fromMethod.Invoke(null, new[] {fromValue, fromUnitValue}); // ex: Length quantity = UnitsNet.Length.From(5, LengthUnit.Meter)
@@ -117,6 +119,8 @@ public static bool TryConvertByName(double inputValue, string quantityName, stri
117119
{
118120
try
119121
{
122+
// TODO Reimplement to avoid exceptions where possible, as Try methods are generally recommended for performance and this is cheating
123+
// https://msdn.microsoft.com/en-us/library/ms229009(v=vs.100).aspx
120124
result = ConvertByName(inputValue, quantityName, fromUnit, toUnit);
121125
return true;
122126
}
@@ -187,10 +191,13 @@ public static double ConvertByAbbreviation(double fromValue, string quantityName
187191
/// <param name="culture">Culture to parse abbreviations with.</param>
188192
/// <example>double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500</example>
189193
/// <returns>Output value as the result of converting to <paramref name="toUnitAbbrev" />.</returns>
194+
/// <exception cref="QuantityNotFoundException">No quantity types match the <paramref name="quantityName"/>.</exception>
195+
/// <exception cref="UnitNotFoundException">No unit types match the prefix of <paramref name="quantityName"/> or no units are mapped to the abbreviation.</exception>
196+
/// <exception cref="AmbiguousUnitParseException">More than one unit matches the abbrevation.</exception>
190197
public static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, string culture)
191198
{
192-
Type quantityType = UnitsNetAssembly.GetType($"{QuantityNamespace}.{quantityName}"); // ex: UnitsNet.Length struct
193-
Type unitType = UnitsNetAssembly.GetType($"{UnitTypeNamespace}.{quantityName}Unit"); // ex: UnitsNet.Units.LengthUnit enum
199+
Type quantityType = GetQuantityType(quantityName);
200+
Type unitType = GetUnitType(quantityName);
194201

195202
UnitSystem unitSystem = UnitSystem.GetCached(culture);
196203
object fromUnitValue = unitSystem.Parse(fromUnitAbbrev, unitType); // ex: ("m", LengthUnit) => LengthUnit.Meter
@@ -270,6 +277,8 @@ public static bool TryConvertByAbbreviation(double fromValue, string quantityNam
270277
{
271278
try
272279
{
280+
// TODO Reimplement to avoid exceptions where possible, as Try methods are generally recommended for performance and this is cheating
281+
// https://msdn.microsoft.com/en-us/library/ms229009(v=vs.100).aspx
273282
result = ConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, culture);
274283
return true;
275284
}
@@ -321,5 +330,47 @@ private static bool HasParameterTypes(MethodInfo methodInfo, params Type[] expec
321330

322331
return true;
323332
}
333+
334+
335+
/// <summary>
336+
/// Parse a unit by the unit enum type <paramref name="unitType" /> and a unit enum value <paramref name="unitName" />>
337+
/// </summary>
338+
/// <param name="unitType">Unit type, such as <see cref="LengthUnit" />.</param>
339+
/// <param name="unitName">Unit name, such as "Meter" corresponding to <see cref="LengthUnit.Meter" />.</param>
340+
/// <returns>Unit enum value, such as <see cref="LengthUnit.Meter" /> boxed as an object.</returns>
341+
/// <exception cref="UnitNotFoundException">No unit values match the <paramref name="unitName" />.</exception>
342+
private static object ParseUnit(Type unitType, string unitName)
343+
{
344+
object unitValue; // ex: LengthUnit.Meter
345+
try
346+
{
347+
unitValue = Enum.Parse(unitType, unitName);
348+
}
349+
catch (Exception e)
350+
{
351+
var e2 = new UnitNotFoundException($"Unit not found [{unitName}].", e);
352+
e2.Data["unitName"] = unitName;
353+
throw e2;
354+
}
355+
return unitValue;
356+
}
357+
358+
private static Type GetUnitType(string quantityName)
359+
{
360+
string unitTypeName = $"{UnitTypeNamespace}.{quantityName}Unit";
361+
Type unitType = UnitsNetAssembly.GetType(unitTypeName); // ex: UnitsNet.Units.LengthUnit enum
362+
if (unitType == null)
363+
throw new UnitNotFoundException($"Unit type name not found: {unitTypeName}");
364+
return unitType;
365+
}
366+
367+
private static Type GetQuantityType(string quantityName)
368+
{
369+
string quantityTypeName = $"{QuantityNamespace}.{quantityName}";
370+
Type quantityType = UnitsNetAssembly.GetType(quantityTypeName); // ex: UnitsNet.Length struct
371+
if (quantityType == null)
372+
throw new QuantityNotFoundException($"Quantity type name not found: {quantityTypeName}");
373+
return quantityType;
374+
}
324375
}
325376
}

UnitsNet/UnitNotFoundException.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright © 2007 Andreas Gullberg Larsen ([email protected]).
2+
// https://github.com/anjdreas/UnitsNet
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
using System;
23+
24+
namespace UnitsNet
25+
{
26+
/// <summary>
27+
/// Unit was not found. This is typically thrown for dynamic conversions,
28+
/// such as <see cref="UnitConverter.ConvertByName" />.
29+
/// </summary>
30+
#if WINDOWS_UWP
31+
internal
32+
#else
33+
public
34+
#endif
35+
class UnitNotFoundException : UnitsNetException
36+
{
37+
public UnitNotFoundException()
38+
{
39+
}
40+
41+
public UnitNotFoundException(string message) : base(message)
42+
{
43+
}
44+
45+
public UnitNotFoundException(string message, Exception innerException) : base(message, innerException)
46+
{
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)