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

Implemented Length type #63 #68

Merged
merged 6 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) { }
}
66 changes: 66 additions & 0 deletions src/Nox.Types/Common/MeasurementConversionFactor.cs
rochar marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Linq;

namespace Nox.Common;

public class MeasurementConversionFactor
{
private const string Foot = "Foot";
private const string Meter = "Meter";
private const string Kilometer = "Kilometer";
private const string Mile = "Mile";
private static readonly string[] _lengthUnits = new[] { Foot, Meter, Kilometer, Mile };
rochar marked this conversation as resolved.
Show resolved Hide resolved

private const string SquareFoot = "SquareFoot";
private const string SquareMeter = "SquareMeter";
private static readonly string[] _areaUnits = new[] { SquareFoot, SquareMeter };

public double Value { get; }

public MeasurementConversionFactor(Enum sourceUnit, Enum targetUnit)
: this(sourceUnit.ToString(), targetUnit.ToString())
{
}

public MeasurementConversionFactor(string sourceUnit, string targetUnit)
{
Value = ResolveConversionFactor(sourceUnit, targetUnit)
?? throw new NotImplementedException($"No conversion defined from {sourceUnit} to {targetUnit}.");
}

private double? ResolveConversionFactor(string sourceUnit, string targetUnit)
{
return IsSupported(sourceUnit) && IsSupported(targetUnit)
? ResolveConversionFactorForSupportedUnits(sourceUnit, targetUnit)
: null;
}

private bool IsSupported(string unit)
=> _lengthUnits.Contains(unit) || _areaUnits.Contains(unit);

private double? ResolveConversionFactorForSupportedUnits(string sourceUnit, string targetUnit)
{
if (sourceUnit == targetUnit)
return 1;

else if (sourceUnit == Foot && targetUnit == Meter)
return 0.30480000033;

else if (sourceUnit == Meter && targetUnit == Foot)
return 3.28083989142;

else if (sourceUnit == Kilometer && targetUnit == Mile)
return 0.62137119102;

else if (sourceUnit == Mile && targetUnit == Kilometer)
return 1.60934400315;

else if (sourceUnit == SquareFoot && targetUnit == SquareMeter)
return 0.09290304;

else if (sourceUnit == SquareMeter && targetUnit == SquareFoot)
return 10.76391042;

return null;
}
}
17 changes: 5 additions & 12 deletions src/Nox.Types/Types/Area/Area.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Nox.Common;
using System;
using System.Collections.Generic;

namespace Nox.Types;
Expand Down Expand Up @@ -104,18 +105,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(Unit, targetUnit).Value;
return Round(Value * factor);
}

private static QuantityValue Round(QuantityValue value)
Expand Down
17 changes: 5 additions & 12 deletions src/Nox.Types/Types/Distance/Distance.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Nox.Common;
using System;
using System.Collections.Generic;

namespace Nox.Types;
Expand Down Expand Up @@ -122,18 +123,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(Unit, targetUnit).Value;
return Round(Value * factor);
}

private static QuantityValue Round(QuantityValue value)
Expand Down
115 changes: 112 additions & 3 deletions src/Nox.Types/Types/Length/Length.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,118 @@
using Nox.Common;
using System;
using System.Collections.Generic;

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)
{
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()
rochar marked this conversation as resolved.
Show resolved Hide resolved
=> $"{Value:G} {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(Unit, 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,
Meter
}

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}.")
};
}
}
72 changes: 72 additions & 0 deletions tests/Nox.Types.Tests/Common/MeasurementConversionFactorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Nox.Common;

namespace Nox.Types.Tests.Common;

public class MeasurementConversionFactorTests
{
[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromFootToMeter_ReturnsValue()
{
var factor = new MeasurementConversionFactor("Foot", "Meter");

Assert.Equal(0.30480000033, factor.Value);
rochar marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromMeterToFoot_ReturnsValue()
{
var factor = new MeasurementConversionFactor("Meter", "Foot");

Assert.Equal(3.28083989142, factor.Value);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromKilometerToMile_ReturnsValue()
{
var factor = new MeasurementConversionFactor("Kilometer", "Mile");

Assert.Equal(0.62137119102, factor.Value);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromMileToKilometer_ReturnsValue()
{
var factor = new MeasurementConversionFactor("Mile", "Kilometer");

Assert.Equal(1.60934400315, factor.Value);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromSquareFootToSquareMeter_ReturnsValue()
{
var factor = new MeasurementConversionFactor("SquareFoot", "SquareMeter");

Assert.Equal(0.09290304, factor.Value);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromSquareMeterToSquareFoot_ReturnsValue()
{
var factor = new MeasurementConversionFactor("SquareMeter", "SquareFoot");

Assert.Equal(10.76391042, factor.Value);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_WithSameSourceAndTargetUnit_ReturnsValue()
{
var factor = new MeasurementConversionFactor("Foot", "Foot");

Assert.Equal(1, factor.Value);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_WithUnsupportedConversion_ThrowsException()
{
var exception = Assert.Throws<NotImplementedException>(() => _ =
new MeasurementConversionFactor("SquareMeter", "Meter")
);

Assert.Equal("No conversion defined from SquareMeter to Meter.", exception.Message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public void Configure(EntityTypeBuilder<Country> builder)
builder.Property(e => e.InternetDomain).HasConversion<InternetDomainConverter>();
builder.Property(e => e.CountryCode3).HasConversion<CountryCode3Converter>();
builder.Property(e => e.IPAddress).HasConversion<IpAddressConverter>();
builder.Property(e => e.LongestHikingTrailInMeters).HasConversion<LengthToMeterConverter>();

// Configure Multi-value ValueObjects
builder.OwnsOne(e => e.LatLong).Ignore(p => p.Value);
Expand Down
5 changes: 5 additions & 0 deletions tests/Nox.Types.Tests/EntityFrameworkTests/Models/Country.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@ public sealed class Country
/// Gets or sets the IP Address.
/// </summary>
public IpAddress IPAddress { get; set; } = null!;

/// <summary>
/// Gets or sets the longest hiking trail in meters.
/// </summary>
public Length LongestHikingTrailInMeters { get; set; } = null!;
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public void Countries_CanRead_LatLong()
CountryCode3 = CountryCode3.From("CHE"),
IPAddress = IpAddress.From("102.129.143.255"),
DateTimeRange = DateTimeRange.From(new DateTime(2023, 01, 01), new DateTime(2023, 02, 01)),
LongestHikingTrailInMeters = Length.From(390_000),
};
DbContext.Countries.Add(newItem);
DbContext.SaveChanges();
Expand Down Expand Up @@ -70,6 +71,7 @@ public void AddedItemShouldGetGeneratedId()
InternetDomain = InternetDomain.From("admin.ch"),
CountryCode3 = CountryCode3.From("CHE"),
IPAddress = IpAddress.From("102.129.143.255"),
LongestHikingTrailInMeters = Length.From(390_000),
};
DbContext.Countries.Add(newItem);
DbContext.SaveChanges();
Expand Down Expand Up @@ -102,5 +104,7 @@ public void AddedItemShouldGetGeneratedId()
Assert.Equal("102.129.143.255", item.IPAddress.Value);
Assert.Equal(new DateTime(2023, 01, 01), item.DateTimeRange.Start);
Assert.Equal(new DateTime(2023, 02, 01), item.DateTimeRange.End);
Assert.Equal(390_000, item.LongestHikingTrailInMeters.Value);
Assert.Equal(LengthTypeUnit.Meter, item.LongestHikingTrailInMeters.Unit);
}
}
Loading