Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,18 @@
<PackageReference Include="MinVer" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<Using Include="LanguageExt" />
<Using Include="LanguageExt.Common" />
<Using Include="LanguageExt.Prelude">
<Static>True</Static>
</Using>
<Using Include="LanguageExt.Parsec" />
<Using Include="LanguageExt.Parsec.Char">
<Static>True</Static>
</Using>
<Using Include="LanguageExt.Parsec.Prim">
<Static>True</Static>
</Using>
</ItemGroup>
</Project>
114 changes: 114 additions & 0 deletions Source/Messaging/ComparableType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
namespace Fossa.Messaging;

/// <summary>
/// A wrapper around <see cref="Type"/> that provides comparability.
/// </summary>
/// <param name="value">The <see cref="Type"/> to wrap.</param>
public readonly struct ComparableType(Type value) : IComparable<ComparableType>, IEquatable<ComparableType>, IComparable
{
/// <summary>
/// Gets the underlying <see cref="Type"/>.
/// </summary>
public readonly Type Value { get; } = value;

/// <summary>
/// Implicitly converts a <see cref="Type"/> to a <see cref="ComparableType"/>.
/// </summary>
/// <param name="t">The <see cref="Type"/>.</param>
public static implicit operator ComparableType(Type t) => new(t);

/// <summary>
/// Implicitly converts a <see cref="ComparableType"/> to a <see cref="Type"/>.
/// </summary>
/// <param name="c">The <see cref="ComparableType"/>.</param>
public static implicit operator Type(ComparableType c) => c.Value;

/// <summary>
/// Compares two <see cref="ComparableType"/> values for equality.
/// </summary>
/// <param name="left">The left <see cref="ComparableType"/>.</param>
/// <param name="right">The right <see cref="ComparableType"/>.</param>
/// <returns><c>true</c> if the values are equal, <c>false</c> otherwise.</returns>
public static bool operator ==(ComparableType left, ComparableType right) => left.Equals(right);

/// <summary>
/// Compares two <see cref="ComparableType"/> values for inequality.
/// </summary>
/// <param name="left">The left <see cref="ComparableType"/>.</param>
/// <param name="right">The right <see cref="ComparableType"/>.</param>
/// <returns><c>true</c> if the values are not equal, <c>false</c> otherwise.</returns>
public static bool operator !=(ComparableType left, ComparableType right) => !(left == right);

/// <summary>
/// Compares two <see cref="ComparableType"/> values to determine which is less.
/// </summary>
/// <param name="left">The left <see cref="ComparableType"/>.</param>
/// <param name="right">The right <see cref="ComparableType"/>.</param>
/// <returns><c>true</c> if the left value is less than the right value, <c>false</c> otherwise.</returns>
public static bool operator <(ComparableType left, ComparableType right) => left.CompareTo(right) < 0;

/// <summary>
/// Compares two <see cref="ComparableType"/> values to determine if the left is less than or equal to the right.
/// </summary>
/// <param name="left">The left <see cref="ComparableType"/>.</param>
/// <param name="right">The right <see cref="ComparableType"/>.</param>
/// <returns><c>true</c> if the left value is less than or equal to the right value, <c>false</c> otherwise.</returns>
public static bool operator <=(ComparableType left, ComparableType right) => left.CompareTo(right) <= 0;

/// <summary>
/// Compares two <see cref="ComparableType"/> values to determine which is greater.
/// </summary>
/// <param name="left">The left <see cref="ComparableType"/>.</param>
/// <param name="right">The right <see cref="ComparableType"/>.</param>
/// <returns><c>true</c> if the left value is greater than the right value, <c>false</c> otherwise.</returns>
public static bool operator >(ComparableType left, ComparableType right) => left.CompareTo(right) > 0;

/// <summary>
/// Compares two <see cref="ComparableType"/> values to determine if the left is greater than or equal to the right.
/// </summary>
/// <param name="left">The left <see cref="ComparableType"/>.</param>
/// <param name="right">The right <see cref="ComparableType"/>.</param>
/// <returns><c>true</c> if the left value is greater than or equal to the right value, <c>false</c> otherwise.</returns>
public static bool operator >=(ComparableType left, ComparableType right) => left.CompareTo(right) >= 0;

/// <summary>
/// Converts a <see cref="Type"/> to a <see cref="ComparableType"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> to convert.</param>
/// <returns>A <see cref="ComparableType"/>.</returns>
public static ComparableType FromType(Type type) => new(type);

/// <inheritdoc/>
public int CompareTo(ComparableType other) =>
string.Compare(this.Value.FullName, other.Value.FullName, StringComparison.OrdinalIgnoreCase);

/// <inheritdoc/>
public int CompareTo(object? obj) =>
obj switch
{
null => 1,
ComparableType other => this.CompareTo(other),
_ => throw new ArgumentException($"Object is not a {nameof(ComparableType)}", nameof(obj)),
};

/// <inheritdoc/>
public bool Equals(ComparableType other) =>
this.Value == other.Value;

/// <inheritdoc/>
public override bool Equals(object? obj) =>
obj is ComparableType other && this.Equals(other);

/// <inheritdoc/>
public override int GetHashCode() =>
this.Value?.GetHashCode() ?? 0;

/// <inheritdoc/>
public override string ToString() => this.Value.FullName ?? string.Empty;

/// <summary>
/// Converts a <see cref="ComparableType"/> to a <see cref="Type"/>.
/// </summary>
/// <returns>The underlying <see cref="Type"/>.</returns>
public Type ToType() => this.Value;
}
60 changes: 60 additions & 0 deletions Source/Messaging/MessageMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace Fossa.Messaging;

/// <summary>
/// Provides a mapping between message types and their corresponding integer identifiers for message serialization and
/// deserialization.
/// </summary>
/// <remarks>Use this class to resolve message type IDs to .NET types and vice versa when processing messages. The
/// mapping is fixed at construction and includes predefined message types such as CompanyChanged, EmployeeChanged, and
/// others. This class is typically used in scenarios where messages are identified by integer IDs and need to be
/// associated with their .NET type for further processing.</remarks>
public class MessageMap
{
private readonly BiMap<ComparableType, int> messageTypeBiMap;

/// <summary>
/// Initializes a new instance of the <see cref="MessageMap"/> class.
/// </summary>
public MessageMap()
{
BiMap<ComparableType, int> xMessageTypeBiMap = [];

RegisterMessageTypes(ref xMessageTypeBiMap);
this.messageTypeBiMap = xMessageTypeBiMap;
}

/// <summary>
/// Gets the message type associated with the specified message type ID.
/// </summary>
/// <param name="messageTypeID">The ID of the message for which to obtain the identifier.</param>
/// <returns>The <see cref="Type"/> corresponding to the given message type ID.</returns>
/// <exception cref="KeyNotFoundException">Thrown if the specified message type ID does not have an associated type.</exception>
public Type GetMessageType(int messageTypeID) =>
this.messageTypeBiMap.Find(messageTypeID)
.IfNone(() => throw new KeyNotFoundException($"Message type not found for ID {messageTypeID}"));

/// <summary>
/// Retrieves the unique identifier associated with the specified message type.
/// </summary>
/// <param name="messageType">The type of the message for which to obtain the identifier. Cannot be null.</param>
/// <returns>The identifier corresponding to the specified message type.</returns>
/// <exception cref="KeyNotFoundException">Thrown if the specified message type does not have an associated identifier.</exception>
public int GetMessageTypeID(Type messageType) =>
this.messageTypeBiMap.Find(messageType)
.IfNone(() => throw new KeyNotFoundException($"Message type ID not found for type {messageType.FullName}"));

private static void RegisterMessageType<TKey>(int value, ref BiMap<ComparableType, int> messageTypeBiMap) =>
messageTypeBiMap = messageTypeBiMap.Add(typeof(TKey), value);

private static void RegisterMessageTypes(ref BiMap<ComparableType, int> messageTypeBiMap)
{
RegisterMessageType<CompanyChanged>(64169988, ref messageTypeBiMap);
RegisterMessageType<CompanyDeleted>(64169993, ref messageTypeBiMap);
RegisterMessageType<EmployeeChanged>(64171400, ref messageTypeBiMap);
RegisterMessageType<EmployeeDeleted>(64171404, ref messageTypeBiMap);
RegisterMessageType<BranchChanged>(64171407, ref messageTypeBiMap);
RegisterMessageType<BranchDeleted>(64171411, ref messageTypeBiMap);
RegisterMessageType<DepartmentChanged>(64171414, ref messageTypeBiMap);
RegisterMessageType<DepartmentDeleted>(64171418, ref messageTypeBiMap);
}
}
26 changes: 26 additions & 0 deletions Tests/.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<Format>cobertura</Format>
<Exclude>
[Fossa.Messaging]*Schema*;
[Fossa.Messaging]*Changed;
[Fossa.Messaging]*Deleted;
[Fossa.Messaging.Test]*
</Exclude>
<ExcludeByAttribute>
System.CodeDom.Compiler.GeneratedCodeAttribute,
System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute,
System.Diagnostics.DebuggerNonUserCodeAttribute
</ExcludeByAttribute>
<ExcludeByFile>
**/Schema.cs
</ExcludeByFile>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
3 changes: 3 additions & 0 deletions Tests/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@
<PackageReference Include="NSubstitute" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>
146 changes: 146 additions & 0 deletions Tests/Messaging.Test/ComparableTypeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
namespace Fossa.Messaging.Test;

public class ComparableTypeTests
{
private readonly Type type1 = typeof(string);
private readonly Type type2 = typeof(int);

[Fact]
public void ImplicitConversion_FromType_ShouldWork()
{
// Arrange
ComparableType comparableType = this.type1;

// Assert
Assert.Equal(this.type1, comparableType.Value);
}

[Fact]
public void ImplicitConversion_ToType_ShouldWork()
{
// Act
Type type = new ComparableType(this.type1);

// Assert
Assert.Equal(this.type1, type);
}

[Fact]
public void FromType_ShouldWork()
{
// Arrange
var comparableType = ComparableType.FromType(this.type1);

// Assert
Assert.Equal(this.type1, comparableType.Value);
}

[Fact]
public void ToType_ShouldWork()
{
// Arrange
var comparableType = new ComparableType(this.type1);

// Act
var type = comparableType.ToType();

// Assert
Assert.Equal(this.type1, type);
}

[Fact]
public void EqualityOperators_ShouldWork()
{
// Arrange
var comparableType1 = new ComparableType(this.type1);
var comparableType2 = new ComparableType(this.type1);
var comparableType3 = new ComparableType(this.type2);

// Assert
Assert.True(comparableType1 == comparableType2);
Assert.False(comparableType1 == comparableType3);
Assert.False(comparableType1 != comparableType2);
Assert.True(comparableType1 != comparableType3);
}

[Fact]
public void ComparisonOperators_ShouldWork()
{
// Arrange
var comparableType1 = new ComparableType(typeof(string));
var comparableType2 = new ComparableType(typeof(int));

// Assert
Assert.True(comparableType1 > comparableType2);
Assert.True(comparableType1 >= comparableType2);
Assert.False(comparableType1 < comparableType2);
Assert.False(comparableType1 <= comparableType2);
}

[Fact]
public void Equals_ShouldWork()
{
// Arrange
var comparableType1 = new ComparableType(this.type1);
var comparableType2 = new ComparableType(this.type1);
var comparableType3 = new ComparableType(this.type2);

// Assert
Assert.True(comparableType1.Equals(comparableType2));
Assert.False(comparableType1.Equals(comparableType3));
Assert.True(comparableType1.Equals((object)comparableType2));
Assert.False(comparableType1.Equals((object)comparableType3));
}

[Fact]
public void CompareTo_ShouldWork()
{
// Arrange
var comparableType1 = new ComparableType(typeof(string));
var comparableType2 = new ComparableType(typeof(int));
var comparableType3 = new ComparableType(typeof(string));

// Assert
Assert.True(comparableType1.CompareTo(comparableType2) > 0);
Assert.True(comparableType2.CompareTo(comparableType1) < 0);
Assert.Equal(0, comparableType1.CompareTo(comparableType3));
}

[Fact]
public void CompareTo_WithObject_ShouldWork()
{
// Arrange
var comparableType1 = new ComparableType(typeof(string));
var comparableType2 = new ComparableType(typeof(int));
var comparableType3 = new ComparableType(typeof(string));

// Assert
Assert.True(comparableType1.CompareTo((object)comparableType2) > 0);
Assert.True(comparableType2.CompareTo((object)comparableType1) < 0);
Assert.Equal(0, comparableType1.CompareTo((object)comparableType3));
_ = Assert.Throws<ArgumentException>(() => comparableType1.CompareTo("not a comparable type"));
}

[Fact]
public void GetHashCode_ShouldWork()
{
// Arrange
var comparableType1 = new ComparableType(this.type1);
var comparableType2 = new ComparableType(this.type1);
var comparableType3 = new ComparableType(this.type2);

// Assert
Assert.Equal(comparableType1.GetHashCode(), comparableType2.GetHashCode());
Assert.NotEqual(comparableType1.GetHashCode(), comparableType3.GetHashCode());
}

[Fact]
public void ToString_ShouldWork()
{
// Arrange
var comparableType = new ComparableType(this.type1);

// Assert
Assert.Equal(this.type1.FullName, comparableType.ToString());
}
}
Loading
Loading