Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nox types/nuid type introduce #233

Merged
merged 13 commits into from
Jul 18, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public class NoxSimpleTypeDefinition : DefinitionBase
[IfEquals("Type", NoxType.TranslatedText)]
public TranslatedTextTypeOptions? TranslatedTextTypeOptions { get; set; }

[IfEquals("Type", NoxType.Nuid)]
public NuidTypeOptions? NuidTypeOptions { get; set; }

[IfEquals("Type", NoxType.Year)]
public YearTypeOptions? YearTypeOptions { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Nox.Generator.Common;
using Nox.Solution;
using Nox.Types.EntityFramework.Types;

namespace Nox.Types.EntityFramework.Abstractions
{
Expand All @@ -12,7 +11,7 @@ public abstract class NoxDatabaseConfigurator : INoxDatabaseConfigurator
protected readonly Dictionary<NoxType, INoxTypeDatabaseConfigurator> TypesDatabaseConfigurations = new();

/// <summary>
///
///
/// </summary>
/// <param name="configurators">List of all loaded <see cref="INoxTypeDatabaseConfigurator"/></param>
/// <param name="databaseProviderSpecificOverrides">Configurator type specific to database provider</param>
Expand All @@ -32,12 +31,9 @@ protected NoxDatabaseConfigurator(
}

// Override specific database provider configurators
foreach (var configurator in noxTypeDatabaseConfigurators)
foreach (var configurator in noxTypeDatabaseConfigurators.Where(x => databaseProviderSpecificOverrides.IsInstanceOfType(x)))
{
if (databaseProviderSpecificOverrides.IsInstanceOfType(configurator))
{
TypesDatabaseConfigurations[configurator.ForNoxType] = configurator;
}
TypesDatabaseConfigurations[configurator.ForNoxType] = configurator;
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/Nox.Types.EntityFramework/Types/Nuid/NuidConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace Nox.Types.EntityFramework.Types;

public class NuidConverter : ValueConverter<Nuid, uint>
{
public NuidConverter() : base(nuid => nuid.Value, nuid => Nuid.From(nuid))
{
}
}
30 changes: 30 additions & 0 deletions src/Nox.Types/Common/Base36Converter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text;

namespace Nox.Types.Common;

/// <summary>
/// Utility methods for converting value to Base36 string.
/// </summary>
internal static class Base36Converter
{
private const int Base = 36;
private const string Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

/// <summary>
/// Converts integer value to Base36 string.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string ToBase36(uint value)
{
var result = new StringBuilder(64);

while (value > 0)
{
result.Append(Chars[(int)(value % Base)]);
value /= Base;
}

return result.ToString();
}
}
1 change: 1 addition & 0 deletions src/Nox.Types/Nox.Types.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<InternalsVisibleTo Include="Nox.Types.Tests" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="7.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.3" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
135 changes: 130 additions & 5 deletions src/Nox.Types/Types/Nuid/Nuid.cs
AndreyDegtyarev marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,9 +1,134 @@
using System.Runtime.CompilerServices;
using System.Text;
using System;
using Nox.Types.Common;
using System.Linq;
using System.IO.Hashing;

namespace Nox.Types;

/// <summary>
/// Represents a Nox <see cref="Nuid"/> type and value object.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
public sealed class Nuid : ValueObject<string, Nuid>
/// <summary>
/// Represents a Nox <see cref="Nuid"/> type and value object.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
public sealed class Nuid : ValueObject<uint, Nuid>, IComparable, IComparable<Nuid>, IEquatable<Nuid>
{
public static Nuid From(string textToEncode)
{
return From(textToEncode, new NuidTypeOptions { });
}

public static Nuid From(string textToEncode, NuidTypeOptions options)
{
var unsignedValue = ToUInt32(textToEncode);
var nuid = From(unsignedValue);

return nuid;
}

public string ToHex()
{
return string.Format("{0:X}", Value).PadLeft(8, '0');
}

public string ToBase36()
{
return Base36Converter.ToBase36(Value);
}

public Guid ToGuid()
{
byte[] bytes = new byte[16];
BitConverter
.GetBytes(Value)
.Reverse()
.ToArray()
.CopyTo(bytes, 12);

return new Guid(bytes);
}

public int CompareTo(object? obj)
{
if (obj == null) return 1;

if (obj is not Nuid)
{
throw new ArgumentException("Object must be of type NUID.", nameof(obj));
}

return (uint)obj != Value ? 1 : 0;
}

public int CompareTo(Nuid other)
{
return other.Value != Value ? 1 : 0;
}

#if NET7_0
public override bool Equals([NotNullWhen(true)] object? o)
{
return o is Nuid other && Equals(other);
}
#endif

#if NETSTANDARD2_0

public override bool Equals(object obj)
{
return base.Equals(obj);
}

#endif

public bool Equals(Nuid other)
{
return Value == other.Value;
}

public override int GetHashCode()
{
return (int)Value;
}

public static bool operator ==(Nuid a, Nuid b) => EqualsCore(a, b);

public static bool operator !=(Nuid a, Nuid b) => !EqualsCore(a, b);

public static bool operator <(Nuid a, Nuid b) => a.CompareTo(b) < 0;

public static bool operator >(Nuid a, Nuid b) => a.CompareTo(b) > 0;

public static bool operator <=(Nuid a, Nuid b) => a.CompareTo(b) <= 0;

public static bool operator >=(Nuid a, Nuid b) => a.CompareTo(b) >= 0;

private static bool EqualsCore(in Nuid left, in Nuid right)
{
if (left is null || right is null)
{
return false;
}

uint leftVal = left.Value;
uint rightVal = right.Value;
ref uint rA = ref Unsafe.AsRef(in leftVal);
ref uint rB = ref Unsafe.AsRef(in rightVal);

// Compare each element
return rA == rB;
}

private static uint ToUInt32(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = XxHash64
.Hash(bytes)
.Reverse()
.ToArray();

var nuid = BitConverter.ToUInt32(hash, 0);

return (nuid + uint.MaxValue + 1);
}
}
9 changes: 9 additions & 0 deletions src/Nox.Types/Types/Nuid/NuidTypeOptions.cs
AndreyDegtyarev marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Nox.Types;

public class NuidTypeOptions
{
public string Separator { get; set; } = string.Empty;
public string[] PropertyNames { get; set; } = Array.Empty<string>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public void Configure(EntityTypeBuilder<Country> builder)
builder.Property(e => e.LocalTimeZone).HasConversion<TimeZoneCodeConverter>();
builder.Property(e => e.Uri).HasConversion<UriConverter>();
builder.Property(e => e.IsLandLocked).HasConversion<BooleanConverter>();
builder.Property(e => e.Nuid).HasConversion<NuidConverter>();

// Configure Multi-value ValueObjects
builder.OwnsOne(e => e.LatLong).Ignore(p => p.Value);
Expand Down
19 changes: 13 additions & 6 deletions tests/Nox.Types.Tests/EntityFrameworkTests/Models/Country.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
namespace Nox.Types.Tests.EntityFrameworkTests;

public class CountryId : ValueObject<int, CountryId> { }
public class CountryId : ValueObject<int, CountryId>
{ }

public sealed class Country
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
public CountryId Id { get; set; } = null!;

/// <summary>
/// Gets or sets the name.
/// </summary>
public Text Name { get; set; } = null!;

/// <summary>
/// Gets or sets the population.
/// </summary>
Expand All @@ -36,7 +39,7 @@ public sealed class Country
/// Gets or sets the area in square Kilometers.
/// </summary>
public Area AreaInSqKm { get; set; } = null!;

/// <summary>
/// Gets or sets the culture.
/// </summary>
Expand All @@ -46,12 +49,11 @@ public sealed class Country
/// Gets or sets the country number.
/// </summary>
public CountryNumber CountryNumber { get; set; } = null!;

/// <summary>
/// Gets or sets the month when the most tourists come to the country.
/// </summary>
public Month MonthOfPeakTourism { get; set; } = null!;


/// <summary>
/// Gets or sets the distance in kilometers.
Expand All @@ -62,7 +64,7 @@ public sealed class Country
/// Gets or sets the date time range.
/// </summary>
public DateTimeRange DateTimeRange { get; set; } = null!;

/// <summary>
/// Gets or sets the internet domain associated with the country.
/// </summary>
Expand Down Expand Up @@ -113,4 +115,9 @@ public sealed class Country
/// Gets or sets the IsLandlocked property.
/// </summary>
public Boolean IsLandLocked { get; set; } = null!;
}

/// <summary>
/// Gets or sets the Nuid.
/// </summary>
public Nuid Nuid { get; set; } = null!;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace Nox.Types.Tests.EntityFrameworkTests;
public class NoxTypesEntityFrameworkTests : TestWithSqlite
{
private const string Sample_Uri = "https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName";
private readonly (string NuidStringValue, uint NuidValue) NuidDefinition = ("PropertyNamesWithSeparator", 3697780159);

[Fact]
public async Task DatabaseIsAvailableAndCanBeConnectedTo()
{
Expand Down Expand Up @@ -91,7 +93,8 @@ public void AddedItemShouldGetGeneratedId()
Date = Date.From(new DateTime(2023, 11, 25), new()),
StreetAddressJson = Json.From(JsonSerializer.Serialize(streetAddress, new JsonSerializerOptions { WriteIndented = true })),
LocalTimeZone = TimeZoneCode.From("CET"),
IsLandLocked = Boolean.From(true)
IsLandLocked = Boolean.From(true),
Nuid = Nuid.From(NuidDefinition.NuidStringValue)
};
DbContext.Countries!.Add(newItem);
DbContext.SaveChanges();
Expand Down Expand Up @@ -133,6 +136,7 @@ public void AddedItemShouldGetGeneratedId()

Assert.Equal(Sample_Uri, item.Uri.Value.AbsoluteUri);
Assert.Equal(Sample_Uri, item.Uri.Value.AbsoluteUri);
Assert.Equal(NuidDefinition.NuidValue, item.Nuid.Value);
AssertStreetAddress(streetAddress, item.StreetAddress);
Assert.Equal(JsonSerializer.Serialize(streetAddress), item.StreetAddressJson.Value);
}
Expand Down
26 changes: 25 additions & 1 deletion tests/Nox.Types.Tests/Types/Nuid/NuidTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
// ReSharper disable once CheckNamespace
using FluentAssertions;

namespace Nox.Types.Tests.Types;

public class NuidTests
{
rochar marked this conversation as resolved.
Show resolved Hide resolved
private const string TestStringValue = "!#123TestValue456!#";
private const uint ExpectedNuidValue = 598674021;
private const string ExpectedBase36 = "XTNFW9";
private readonly Guid ExpectedGuid = new("00000000-0000-0000-0000-000023af0a65");

[Fact]
public void FromString_ConstructInstancesFromSameSource_ShouldReturnEqualNuids()
{
var nuidLeft = Nuid.From(TestStringValue);
var nuidRight = Nuid.From(TestStringValue);

nuidLeft.Should().Be(nuidRight);
nuidRight.Value.Should().Be(nuidRight.Value);
nuidLeft.ToGuid().Should().Be(nuidRight.ToGuid());
nuidLeft.ToHex().Should().Be(nuidRight.ToHex());
nuidLeft.ToBase36().Should().Be(nuidRight.ToBase36());
}

[Fact]
public void When_Create_Should()
public void FromString_ConstructNuid_ShouldReturnCertainData()
{
var nuid = Nuid.From(TestStringValue);

nuid.Value.Should().Be(ExpectedNuidValue);
nuid.ToGuid().Should().Be(ExpectedGuid);
nuid.ToBase36().Should().Be(ExpectedBase36);
}
}