Skip to content
Open
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
15 changes: 15 additions & 0 deletions R4Utils/Messaging/Exceptions/AlreadySetUpException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace R4Utils.Messaging.Exceptions
{
/// <summary>
/// Thrown when a set up method has been called more than once.
/// </summary>
public class AlreadySetUpException : Exception
{
public AlreadySetUpException(string methodName)
: base($"The method ${methodName} may not be called more than once.")
{
}
}
}
39 changes: 39 additions & 0 deletions R4Utils/Messaging/Exceptions/MessageException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;

namespace R4Utils.Messaging.Exceptions
{
/// <summary>
/// This <see cref="Exception"/> may be created from
/// a <see cref="Message{TData,TEnum}"/> or a <see cref="MessageContext{TEnum}"/>.
/// </summary>
public class MessageException : Exception
{
/// <summary>
/// Creates an instance from just the <see cref="MessageContext{TEnum}"/> without any data.
/// </summary>
/// <param name="messageInformation">A <see cref="string"/> containing information
/// about the <see cref="MessageContext{TEnum}"/> used to create this instance.</param>
public MessageException(string messageInformation)
: base($"The following message was thrown: {messageInformation}")
{
}

/// <summary>
/// Creates an instance from a <see cref="MessageContext{TEnum}"/> and some <see cref="data"/>.
/// </summary>
/// <param name="messageInformation">A <see cref="string"/> containing information
/// about the <see cref="MessageContext{TEnum}"/> used to create this instance.</param>
/// <param name="data">The data enclosed in the <see cref="Message{TData,TEnum}"/> used
/// to create this instance.</param>
public MessageException(string messageInformation, object data) : base(messageInformation)
{
MessageData = data;
}

/// <summary>
/// The data that was enclosed in the <see cref="Message{TData,TEnum}"/> used to create this instance.
/// Is null when just a <see cref="MessageContext{TEnum}"/> was used to create this instance.
/// </summary>
public object? MessageData { get; init; } = null;
}
}
14 changes: 14 additions & 0 deletions R4Utils/Messaging/Exceptions/NotSetUpException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace R4Utils.Messaging.Exceptions
{
/// <summary>
/// Thrown when using a method that requires set up that was not performed.
/// </summary>
public class NotSetUpException : Exception
{
public NotSetUpException(string name) : base($"Calling ${name} requires setting it up first.")
{
}
}
}
83 changes: 83 additions & 0 deletions R4Utils/Messaging/Message.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Runtime.CompilerServices;
using R4Utils.Messaging.Exceptions;

namespace R4Utils.Messaging
{
/// <summary>
/// Represents a message sent between methods
/// </summary>
/// <typeparam name="TData">The type of the value being returned</typeparam>
/// <typeparam name="TEnum">The type of the <see cref="Enum"/> that is
/// used to infer the <see cref="MessageContext"/></typeparam>
public class Message<TData, TEnum>/* : MessageBase*/ where TEnum : Enum
{
/// <summary>
/// The actual value being returned
/// </summary>
public TData Data { get; init; }

/// <summary>
/// The message context containing more information about this instance
/// </summary>
public MessageContext<TEnum> MessageContext { get; init; }

/// <summary>
/// The function returning the <see cref="string"/> message and severity for an element
/// of <see cref="TEnum"/>, given some <see cref="TData"/>.
/// </summary>
// protected static Func<TEnum, TData, MessageContext<TEnum>> ContextGetter { get; set; } = (enumEntry, data)
// => BasicContextGetter(Convert.ToInt32(enumEntry), typeof(TEnum), data);
protected static Func<TEnum, TData, (string, int)> ContextGetter { get; set; } = (_, _) =>
throw new NotImplementedException();

/// <summary>
/// Whether <see cref="SetUp"/> has already been called without failing
/// </summary>
// ReSharper disable once StaticMemberInGenericType
protected static bool AlreadySetUp { get; private set; } = false;

/// <summary>
/// Call this method to set up the message handling for
/// this specific combination of <see cref="TData"/> and <see cref="TEnum"/>.
/// MUST be called once before the first <see cref="Message{TData, TEnum}"/> can be created.
/// MAY NOT be called thereafter.
/// </summary>
/// <param name="contextGetter">The function returning
/// the <see cref="string"/> message and severity for an element
/// of <see cref="TEnum"/>, given some <see cref="TData"/>.</param>
/// <exception cref="AlreadySetUpException">Thrown when this method was already called.</exception>
public static void SetUp(Func<TEnum, TData, (string, int)> contextGetter)
{
if (AlreadySetUp)
throw new AlreadySetUpException(nameof(SetUp));

ContextGetter = contextGetter;
AlreadySetUp = true;
}

public static Message<TData, TEnum> Create(TData data, TEnum enumEntry,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
if (AlreadySetUp == false/* && AlreadyBasicSetUp == false*/)
throw new NotSetUpException(nameof(Create));

if (data is null)
throw new ArgumentNullException(nameof(data),
$"A ${nameof(Message<TData, TEnum>)} may not be created with null ${nameof(data)}.");

(string message, int severity) = ContextGetter(enumEntry, data);
MessageContext<TEnum> context = MessageContext<TEnum>.Create(enumEntry, message, severity,
sourceFilePath, memberName, sourceLineNumber);
return new(data, context);
}

protected Message(TData data, MessageContext<TEnum> messageContext)
{
Data = data;
MessageContext = messageContext;
}
}
}
55 changes: 55 additions & 0 deletions R4Utils/Messaging/MessageBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using R4Utils.Messaging.Exceptions;

namespace R4Utils.Messaging
{
#if FALSE
/// <summary>
/// Contains information shared for all <see cref="Message{TData}"/> types
/// </summary>
public abstract class MessageBase
{
/// <summary>
/// The <see cref="Enum"/> containing the message context indices
/// </summary>
protected static Type MessageContextIndex { get; private set; } = typeof(object);

/// <summary>
/// The function returning the <see cref="MessageContext"/> for the index in the enum, provided some data
/// </summary>
protected static Func<int, Type, object?, MessageContext> BasicContextGetter { get; private set; } = (_, _, _)
=> throw new NotImplementedException(nameof(BasicContextGetter));

/// <summary>
/// Whether <see cref="BasicSetUp"/> has already been called without failing
/// </summary>
protected static bool AlreadyBasicSetUp { get; private set; } = false;

/// <summary>
/// Call this method to set up the message handling.
/// MUST be called once before the first <see cref="Message{TData, TEnum}"/> can be created.
/// MAY NOT be called thereafter.
/// </summary>
/// <param name="enumType">The <see cref="Enum"/> that identifies the <see cref="MessageContext"/>
/// instances that may be used.</param>
/// <param name="basicContextGetter">The function returning the <see cref="MessageContext"/> for an index
/// in <see cref="enumType"/>, given some <see cref="object"/>.</param>
/// <exception cref="AlreadySetUpException">Thrown when this method was already called.</exception>
/// <exception cref="ArgumentException">Thrown when <see cref="enumType"/> is
/// not an <see cref="Enum"/>.</exception>
public static void BasicSetUp(Type enumType, Func<int, Type, object?, MessageContext> basicContextGetter)
{
if (AlreadyBasicSetUp)
throw new AlreadySetUpException(nameof(BasicSetUp));

if (enumType.IsEnum == false)
throw new ArgumentException($"The provided ${nameof(Type)} is no ${nameof(Enum)}.",
nameof(enumType));

AlreadyBasicSetUp = true;
MessageContextIndex = enumType;
BasicContextGetter = basicContextGetter;
}
}
#endif
}
85 changes: 85 additions & 0 deletions R4Utils/Messaging/MessageContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Text;
using R4Utils.Messaging.Exceptions;

namespace R4Utils.Messaging
{
/// <summary>
/// Represents the context of a <see cref="Message{TData, TEnum}" />
/// </summary>
public class MessageContext<TEnum> where TEnum : Enum
{
/// <summary>
/// The entry of the this <see cref="MessageContext{TEnum}"/> in the <see cref="Enum"/>
/// used to create this instance.
/// </summary>
public TEnum ContextIdentifier { get; init; }

/// <summary>
/// The message that can get displayed to the user.
/// </summary>
public string MessageText { get; init; }

/// <summary>
/// The severity assigned to this instance.
/// The interpretation of this value is not fixed.
/// </summary>
public int Severity { get; init; }

/// <summary>
/// The file containing the caller creating
/// the <see cref="Message{TData,TEnum}"/> for which this instance has been created.
/// </summary>
public string SourceCompilationUnit { get; init; }
/// <summary>
/// The method creating
/// the <see cref="Message{TData,TEnum}"/> for which this instance has been created.
/// </summary>
public string SourceMethod { get; init; }
/// <summary>
/// The line in the <see cref="SourceCompilationUnit"/> in which
/// the <see cref="Message{TData,TEnum}"/> for which this instance has been created,
/// was created.
/// </summary>
public int SourceLine { get; init; }

public static MessageContext<TEnum> Create(TEnum enumEntry, string messageText, int severity,
string sourceCompilationUnit, string sourceMethod, int sourceLine)
=> new(enumEntry, messageText, severity, sourceCompilationUnit, sourceMethod, sourceLine);

protected MessageContext(TEnum contextIdentifier, string messageText, int severity, string sourceCompilationUnit,
string sourceMethod, int sourceLine)
{
ContextIdentifier = contextIdentifier;
MessageText = messageText;
Severity = severity;
SourceCompilationUnit = sourceCompilationUnit;
SourceMethod = sourceMethod;
SourceLine = sourceLine;
}

/// <summary>
/// Get all data from this instance as a <see cref="string"/>.
/// </summary>
/// <returns>A <see cref="string"/> in the form 'Message: ...\n Identifier: ...\n Severity:
/// ...\n Message occurred at ... in line ... in ...'.</returns>
public override string ToString()
{
StringBuilder sb = new();
sb.Append($"Message: \"{MessageText}\"");
sb.Append(Environment.NewLine);
sb.Append($"Identifier: {ContextIdentifier}");
sb.Append(Environment.NewLine);
sb.Append($"Severity: {Severity}");
sb.Append(Environment.NewLine);
sb.Append($"Message occurred at '{SourceCompilationUnit} in line {SourceLine} in {SourceMethod}");
return sb.ToString();
}

/// <summary>
/// Get a <see cref="MessageException"/> from this instance.
/// </summary>
/// <returns>The <see cref="MessageException"/> containing the information about this instance.</returns>
public MessageException AsException() => new(ToString());
}
}