A src/Core/NosSmooth.LocalBinding/Errors/OptionalNotPresentError.cs => src/Core/NosSmooth.LocalBinding/Errors/OptionalNotPresentError.cs +11 -0
@@ 0,0 1,11 @@
+//
+// OptionalNotPresentError.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Remora.Results;
+
+namespace NosSmooth.LocalBinding.Errors;
+
+public record OptionalNotPresentError(string TypeName) : ResultError($"The optional {TypeName} is not present.");<
\ No newline at end of file
A src/Core/NosSmooth.LocalBinding/Optional.cs => src/Core/NosSmooth.LocalBinding/Optional.cs +211 -0
@@ 0,0 1,211 @@
+//
+// Optional.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics.CodeAnalysis;
+using NosSmooth.LocalBinding.Errors;
+using Remora.Results;
+
+namespace NosSmooth.LocalBinding;
+
+/// <summary>
+/// An optional, used mainly for hooks and binding modules,
+/// to make it possible to check whether a module is loaded
+/// in runtime.
+/// </summary>
+/// <typeparam name="T">The type of underlying value.</typeparam>
+public class Optional<T>
+ where T : notnull
+{
+ /// <summary>
+ /// An empty optional (no value present).
+ /// </summary>
+ public static readonly Optional<T> Empty = new();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Optional{T}"/> class.
+ /// </summary>
+ /// <param name="value">The underlying value of the optional. Not present when null.</param>
+ public Optional(T? value = default)
+ {
+ Value = value;
+ }
+
+ /// <summary>
+ /// Gets whether the value is present.
+ /// </summary>
+ [MemberNotNullWhen(true, "Value")]
+ public bool IsPresent => Value is not null;
+
+ /// <summary>
+ /// Gets the underlying value.
+ /// </summary>
+ public T? Value { get; }
+
+ /// <summary>
+ /// Tries to get the underlying value, if it's present.
+ /// If it's not present, value won't be set.
+ /// </summary>
+ /// <param name="value">The underlying value.</param>
+ /// <returns>Whether the value is present.</returns>
+ public bool TryGet([NotNullWhen(true)] out T? value)
+ {
+ value = Value;
+ return IsPresent;
+ }
+
+ /// <summary>
+ /// Tries to execute an action on the value, if it exists.
+ /// </summary>
+ /// <remarks>
+ /// Does nothing, if the value does not exist.
+ /// </remarks>
+ /// <param name="action">The action to execute.</param>
+ /// <returns>Whether the value is present.</returns>
+ public bool TryDo(Action<T> action)
+ {
+ if (IsPresent)
+ {
+ action(Value);
+ }
+
+ return IsPresent;
+ }
+
+ /// <summary>
+ /// Gets something from the underlying value,
+ /// exposing it as optional that is empty,
+ /// in case this optional is empty as well.
+ /// </summary>
+ /// <param name="get">The function to obtain something from the value with.</param>
+ /// <typeparam name="TU">The return type.</typeparam>
+ /// <returns>An optional, present if this optional's value is present.</returns>
+ public Optional<TU> Map<TU>(Func<T, TU> get)
+ where TU : notnull
+ {
+ if (IsPresent)
+ {
+ return get(Value);
+ }
+
+ return Optional<TU>.Empty;
+ }
+
+ /// <summary>
+ /// Gets something from the underlying value like <see cref="Map{TU}"/>.
+ /// </summary>
+ /// <remarks>
+ /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
+ /// instead of returning an optional.
+ /// </remarks>
+ /// <param name="get">The function to obtain something from the value with.</param>
+ /// <typeparam name="TU">The return type.</typeparam>
+ /// <returns>A result, successful in case this optional's value is present.</returns>
+ public Result<TU> MapResult<TU>(Func<T, TU> get)
+ where TU : notnull
+ {
+ if (IsPresent)
+ {
+ return OptionalUtilities.TryGet(() => get(Value));
+ }
+
+ return new OptionalNotPresentError(nameof(T));
+ }
+
+ /// <summary>
+ /// Does something on the underlying value like <see cref="TryDo"/>, but returns a result
+ /// in case the value is not present.
+ /// </summary>
+ /// <param name="get">The function to execute on the value.</param>
+ /// <typeparam name="TU">The return type.</typeparam>
+ /// <returns>A result, successful in case this optional's value is present.</returns>
+ public Result MapResult(Action<T> get)
+ {
+ if (IsPresent)
+ {
+ return OptionalUtilities.Try(() => get(Value));
+ }
+
+ return new OptionalNotPresentError(nameof(T));
+ }
+
+ /// <summary>
+ /// Gets something from the underlying value like <see cref="Map{TU}"/>.
+ /// </summary>
+ /// <remarks>
+ /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
+ /// instead of returning an optional.
+ ///
+ /// The get function returns a result that will be returned if this optional is present.
+ /// </remarks>
+ /// <param name="get">The function to obtain something from the value with.</param>
+ /// <typeparam name="TU">The return type.</typeparam>
+ /// <returns>A result from the function, <see cref="OptionalNotPresentError"/> in case this optional is not present.</returns>
+ public Result<TU> MapResult<TU>(Func<T, Result<TU>> get)
+ where TU : notnull
+ {
+ if (IsPresent)
+ {
+ return get(Value);
+ }
+
+ return new OptionalNotPresentError(nameof(T));
+ }
+
+ /// <summary>
+ /// Does something on the underlying value like <see cref="TryDo"/>, but returns a result
+ /// in case the value is not present.
+ /// </summary>
+ /// <remarks>
+ /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
+ /// instead of returning an optional.
+ ///
+ /// The get function returns a result that will be returned if this optional is present.
+ /// </remarks>
+ /// <param name="get">The function to obtain something from the value with.</param>
+ /// <typeparam name="TU">The return type.</typeparam>
+ /// <returns>A result from the function, <see cref="OptionalNotPresentError"/> in case this optional is not present.</returns>
+ public Result MapResult(Func<T, Result> get)
+ {
+ if (IsPresent)
+ {
+ return get(Value);
+ }
+
+ return new OptionalNotPresentError(nameof(T));
+ }
+
+ /// <summary>
+ /// Forcefully gets the underlying value.
+ /// If it's not present, <see cref="InvalidOperationException"/> will be thrown.
+ /// </summary>
+ /// <remarks>
+ /// Try to use other methods that return results where possible as they are easier to handle.
+ /// </remarks>
+ /// <returns>The underlying value.</returns>
+ /// <exception cref="InvalidOperationException">Thrown in case the value is not present.</exception>
+ public T Get()
+ {
+ if (!IsPresent)
+ {
+ throw new InvalidOperationException
+ (
+ $"Could not get {nameof(T)}. Did you forget to call initialization or was there an error?"
+ );
+ }
+
+ return Value;
+ }
+
+ /// <summary>
+ /// Cast a value to optional.
+ /// </summary>
+ /// <param name="val">The value to cast.</param>
+ /// <returns>The created optional.</returns>
+ public static implicit operator Optional<T>(T val)
+ {
+ return new Optional<T>(val);
+ }
+}<
\ No newline at end of file
A src/Core/NosSmooth.LocalBinding/OptionalUtilities.cs => src/Core/NosSmooth.LocalBinding/OptionalUtilities.cs +69 -0
@@ 0,0 1,69 @@
+//
+// OptionalUtilities.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Remora.Results;
+
+namespace NosSmooth.LocalBinding;
+
+/// <summary>
+/// A utilities that work with members that may not be present or may throw an exception.
+/// </summary>
+public static class OptionalUtilities
+{
+ /// <summary>
+ /// Tries to get value from the function, capturing an exception into Result.
+ /// </summary>
+ /// <param name="get">The function to obtain the value from.</param>
+ /// <typeparam name="T">The type of the value.</typeparam>
+ /// <returns>The value, or exception error if an exception has been thrown.</returns>
+ public static Result<T> TryGet<T>(Func<T> get)
+ {
+ try
+ {
+ return get();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+
+ /// <summary>
+ /// Tries to get value from the function, capturing an exception into Result.
+ /// </summary>
+ /// <param name="get">The function to obtain the value from.</param>
+ /// <typeparam name="T">The type of the value.</typeparam>
+ /// <returns>The value, or exception error if an exception has been thrown.</returns>
+ public static Result<T> TryIGet<T>(Func<Result<T>> get)
+ {
+ try
+ {
+ return get();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+
+ /// <summary>
+ /// Tries to execute an action.
+ /// </summary>
+ /// <param name="get">The action.</param>
+ /// <returns>A result, successful if no exception has been thrown.</returns>
+ public static Result Try(Action get)
+ {
+ try
+ {
+ get();
+ return Result.FromSuccess();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+}<
\ No newline at end of file