Skip to content
This repository has been archived by the owner on Jul 5, 2023. It is now read-only.

Commit

Permalink
Implemented Length type #63 (#68)
Browse files Browse the repository at this point in the history
* added Length value object;

* added EF converter and tests;

* fixed typo;

* added changes related to PR comments;

* added unit tests for measurement related enum comparisson;

---------

Co-authored-by: Dejan Bratic <dejan.bratic@igwplc.com>
  • Loading branch information
dejobratic and Dejan Bratic authored Jun 30, 2023
1 parent 112f838 commit d30d73a
Show file tree
Hide file tree
Showing 17 changed files with 660 additions and 50 deletions.
12 changes: 12 additions & 0 deletions src/Nox.Types.EntityFramework/Types/Length/LengthConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace Nox.Types.EntityFramework.Types;

public class LengthToFootConverter : ValueConverter<Length, double>
{
public LengthToFootConverter() : base(length => (double)length.ToFeet(), lengthValue => Length.FromFeet(lengthValue)) { }
}
public class LengthToMeterConverter : ValueConverter<Length, double>
{
public LengthToMeterConverter() : base(length => (double)length.ToMeters(), lengthValue => Length.FromMeters(lengthValue)) { }
}
37 changes: 37 additions & 0 deletions src/Nox.Types/Common/MeasurementConversionFactor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;

namespace Nox.Common;

internal sealed class MeasurementConversionFactor
{
private static readonly Dictionary<(MeasurementTypeUnit, MeasurementTypeUnit), double> DefinedConversionFactors = new()
{
{ (MeasurementTypeUnit.Foot, MeasurementTypeUnit.Meter), 0.30480000033},
{ (MeasurementTypeUnit.Meter, MeasurementTypeUnit.Foot), 3.28083989142},
{ (MeasurementTypeUnit.Kilometer, MeasurementTypeUnit.Mile), 0.62137119102},
{ (MeasurementTypeUnit.Mile, MeasurementTypeUnit.Kilometer), 1.60934400315},
{ (MeasurementTypeUnit.SquareFoot, MeasurementTypeUnit.SquareMeter), 0.09290304},
{ (MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.SquareFoot), 10.76391042},
};

public double Value { get; }

public MeasurementConversionFactor(MeasurementTypeUnit sourceUnit, MeasurementTypeUnit targetUnit)
{
Value = ResolveConversionFactor(sourceUnit, targetUnit);
}

private static double ResolveConversionFactor(MeasurementTypeUnit sourceUnit, MeasurementTypeUnit targetUnit)
{
var conversion = (sourceUnit, targetUnit);

if (DefinedConversionFactors.ContainsKey(conversion))
return DefinedConversionFactors[conversion];

else if (sourceUnit == targetUnit)
return 1;

throw new NotImplementedException($"No conversion defined from {sourceUnit} to {targetUnit}.");
}
}
14 changes: 14 additions & 0 deletions src/Nox.Types/Common/MeasurementTypeUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Nox.Common;

internal enum MeasurementTypeUnit
{
// Length
Foot = 1,
Meter = 2,
Kilometer = 3,
Mile = 4,

// Area
SquareFoot = 5,
SquareMeter = 6,
}
1 change: 1 addition & 0 deletions src/Nox.Types/Nox.Types.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
</PropertyGroup>
<ItemGroup>
<None Include="..\..\images\nox.png" Pack="true" PackagePath="" />
<InternalsVisibleTo Include="Nox.Types.Tests" />
</ItemGroup>
</Project>
29 changes: 16 additions & 13 deletions src/Nox.Types/Types/Area/Area.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using Nox.Common;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Nox.Types;

Expand Down Expand Up @@ -91,7 +93,16 @@ internal override ValidationResult Validate()
return result;
}

public override string ToString() => $"{Value:G} {Unit.ToSymbol()}";
public override string ToString()
=> $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit.ToSymbol()}";

/// <summary>
/// Returns a string representation of the <see cref="Length"/> object using the specified <see cref="IFormatProvider"/>.
/// </summary>
/// <param name="formatProvider">The format provider for the length value.</param>
/// <returns>A string representation of the <see cref="Length"/> object with the value formatted using the specified <see cref="IFormatProvider"/>.</returns>
public string ToString(IFormatProvider formatProvider)
=> $"{Value.ToString(formatProvider)} {Unit.ToSymbol()}";

protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponents()
{
Expand All @@ -104,18 +115,10 @@ protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponen
private QuantityValue? _squareFeet;
public QuantityValue ToSquareFeet() => (_squareFeet ??= GetAreaIn(AreaTypeUnit.SquareFoot));

private QuantityValue GetAreaIn(AreaTypeUnit unit)
private QuantityValue GetAreaIn(AreaTypeUnit targetUnit)
{
if (Unit == unit)
return Round(Value);

else if (Unit == AreaTypeUnit.SquareMeter && unit == AreaTypeUnit.SquareFoot)
return Round(Value * 10.76391042);

else if (Unit == AreaTypeUnit.SquareFoot && unit == AreaTypeUnit.SquareMeter)
return Round(Value * 0.09290304);

throw new NotImplementedException($"No conversion defined from {Unit} to {unit}.");
var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
return Round(Value * factor);
}

private static QuantityValue Round(QuantityValue value)
Expand Down
4 changes: 2 additions & 2 deletions src/Nox.Types/Types/Area/AreaTypeUnit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace Nox.Types;

public enum AreaTypeUnit
{
SquareFoot,
SquareMeter,
SquareFoot = 5,
SquareMeter = 6,
}

public static class AreaTypeUnitExtensions
Expand Down
29 changes: 16 additions & 13 deletions src/Nox.Types/Types/Distance/Distance.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using Nox.Common;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Nox.Types;

Expand Down Expand Up @@ -112,7 +114,16 @@ protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponen
yield return new KeyValuePair<string, object>(nameof(Value), ToKilometers());
}

public override string ToString() => $"{Value:G} {Unit.ToSymbol()}";
public override string ToString()
=> $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit.ToSymbol()}";

/// <summary>
/// Returns a string representation of the <see cref="Length"/> object using the specified <see cref="IFormatProvider"/>.
/// </summary>
/// <param name="formatProvider">The format provider for the length value.</param>
/// <returns>A string representation of the <see cref="Length"/> object with the value formatted using the specified <see cref="IFormatProvider"/>.</returns>
public string ToString(IFormatProvider formatProvider)
=> $"{Value.ToString(formatProvider)} {Unit.ToSymbol()}";

private QuantityValue? _kilometers;

Expand All @@ -122,18 +133,10 @@ protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponen

public QuantityValue ToMiles() => (_miles ??= GetDistanceIn(DistanceTypeUnit.Mile));

private QuantityValue GetDistanceIn(DistanceTypeUnit unit)
private QuantityValue GetDistanceIn(DistanceTypeUnit targetUnit)
{
if (Unit == unit)
return Round(Value);

else if (Unit == DistanceTypeUnit.Kilometer && unit == DistanceTypeUnit.Mile)
return Round(Value * 0.62137119102);

else if (Unit == DistanceTypeUnit.Mile && unit == DistanceTypeUnit.Kilometer)
return Round(Value * 1.60934400315);

throw new NotImplementedException($"No conversion defined from {Unit} to {unit}.");
var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
return Round(Value * factor);
}

private static QuantityValue Round(QuantityValue value)
Expand Down
4 changes: 2 additions & 2 deletions src/Nox.Types/Types/Distance/DistanceTypeUnit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace Nox.Types;

public enum DistanceTypeUnit
{
Kilometer,
Mile
Kilometer = 3,
Mile = 4,
}

public static class DistanceTypeUnitExtensions
Expand Down
123 changes: 120 additions & 3 deletions src/Nox.Types/Types/Length/Length.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,126 @@
using Nox.Common;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Nox.Types;

/// <summary>
/// Represents a Nox <see cref="Length"/> type and value object.
/// </summary>
public sealed class Length : ValueObject<QuantityValue, Length>
{
private const int QuantityValueDecimalPrecision = 6;

public LengthTypeUnit Unit { get; private set; } = LengthTypeUnit.Meter;

public Length() { Value = 0; }

/// <summary>
/// Creates a new instance of <see cref="Length"/> object in meters.
/// </summary>
/// <param name="value">The value to create the <see cref="Length"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static Length FromMeters(QuantityValue value)
=> From(value, LengthTypeUnit.Meter);

/// <summary>
/// Creates a new instance of <see cref="Length"/> object in feet.
/// </summary>
/// <param name="value">The origin value to create the <see cref="Length"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static Length FromFeet(QuantityValue value)
=> From(value, LengthTypeUnit.Foot);

/// <summary>
/// Represents a Nox <see cref="Length"/> type and value object.
/// Creates a new instance of <see cref="Length"/> object in meters.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
public sealed class Length : ValueObject<QuantityValue, Length>
/// <param name="value">The value to create the <see cref="Length"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public new static Length From(QuantityValue value)
=> From(value, LengthTypeUnit.Meter);

/// <summary>
/// Creates a new instance of <see cref="Length"/> object with the specified <see cref="LengthTypeUnit"/>.
/// </summary>
/// <param name="value">The value to create the <see cref="Length"/> with</param>
/// <param name="unit">The <see cref="LengthTypeUnit"/> to create the <see cref="Length"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static Length From(QuantityValue value, LengthTypeUnit unit)
{
var newObject = new Length
{
Value = Round(value),
Unit = unit,
};

var validationResult = newObject.Validate();

if (!validationResult.IsValid)
{
throw new TypeValidationException(validationResult.Errors);
}

return newObject;
}

/// <summary>
/// Validates a <see cref="Length"/> object.
/// </summary>
/// <returns>true if the <see cref="Length"/> value is valid.</returns>
internal override ValidationResult Validate()
{
var result = Value.Validate();

if (Value < 0 && !double.IsNaN((double)Value) && !double.IsInfinity((double)Value))
{
result.Errors.Add(new ValidationFailure(nameof(Value), $"Could not create a Nox Length type as negative length value {Value} is not allowed."));
}

if (!Enum.IsDefined(typeof(LengthTypeUnit), Unit))
{
result.Errors.Add(new ValidationFailure(nameof(Unit), $"Could not create a Nox Length type as unit {Unit} is not supported."));
}

return result;
}

protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponents()
{
yield return new KeyValuePair<string, object>(nameof(Value), ToMeters());
}

public override string ToString()
=> $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit.ToSymbol()}";

/// <summary>
/// Returns a string representation of the <see cref="Length"/> object using the specified <see cref="IFormatProvider"/>.
/// </summary>
/// <param name="formatProvider">The format provider for the length value.</param>
/// <returns>A string representation of the <see cref="Length"/> object with the value formatted using the specified <see cref="IFormatProvider"/>.</returns>
public string ToString(IFormatProvider formatProvider)
=> $"{Value.ToString(formatProvider)} {Unit.ToSymbol()}";

private QuantityValue? _meters;

public QuantityValue ToMeters()
=> (_meters ??= GetLengthIn(LengthTypeUnit.Meter));

private QuantityValue? _feet;

public QuantityValue ToFeet()
=> (_feet ??= GetLengthIn(LengthTypeUnit.Foot));

private QuantityValue GetLengthIn(LengthTypeUnit targetUnit)
{
var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
return Round(Value * factor);
}

private static QuantityValue Round(QuantityValue value)
=> Math.Round((double)value, QuantityValueDecimalPrecision);
}
22 changes: 22 additions & 0 deletions src/Nox.Types/Types/Length/LengthTypeUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace Nox.Types;

public enum LengthTypeUnit
{
Foot = 1,
Meter = 2,
}

public static class LengthTypeUnitExtensions
{
public static string ToSymbol(this LengthTypeUnit unit)
{
return unit switch
{
LengthTypeUnit.Foot => "ft",
LengthTypeUnit.Meter => "m",
_ => throw new NotImplementedException($"No symbol defined for unit {unit}.")
};
}
}
Loading

0 comments on commit d30d73a

Please sign in to comment.