Skip to content

Commit

Permalink
Added additional checks to Result<T> and NullableResult<T> types, add…
Browse files Browse the repository at this point in the history
…ed docs, v1.0.2
  • Loading branch information
matsakiv committed Aug 15, 2024
1 parent 2d61b26 commit db1d4f0
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 90 deletions.
47 changes: 42 additions & 5 deletions Incendium.Result/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
namespace Incendium
{
/// <summary>
/// Represents error struct that contains code, message and optionally exception
/// Represents error struct that contains code, message and exception
/// </summary>
/// <param name="code">Error code</param>
/// <param name="message">Error message</param>
/// <param name="exception">Inner exception</param>
public readonly struct Error(int code, string message, Exception? exception)
public readonly struct Error(int code, string? message, Exception? exception)
{
/// <summary>
/// Gets error code
Expand All @@ -17,20 +17,57 @@ public readonly struct Error(int code, string message, Exception? exception)
/// <summary>
/// Gets error message
/// </summary>
public string Message { get; init; } = message;
public string? Message { get; init; } = message;
/// <summary>
/// Gets optional inner exception
/// Gets inner exception
/// </summary>
public Exception? Exception { get; init; } = exception;

/// <summary>
/// Initialize a new instance of the Error struct with a specified error code and message
/// Initialize a new instance of the error struct with a specified error code
/// </summary>
/// <param name="code">Error code</param>
public Error(int code)
: this(code, message: null, exception: null)
{
}

/// <summary>
/// Initialize a new instance of the error struct with a specified error message
/// </summary>
/// <param name="message">Error message</param>
public Error(string message)
: this(code: 0, message: message, exception: null)
{
}

/// <summary>
/// Initialize a new instance of the error struct with a specified inner exception
/// </summary>
/// <param name="exception">Inner exception</param>
public Error(Exception exception)
: this(code: 0, message: null, exception: exception)
{
}

/// <summary>
/// Initialize a new instance of the error struct with a specified error code and message
/// </summary>
/// <param name="code">Error code</param>
/// <param name="message">Error message</param>
public Error(int code, string message)
: this(code, message, exception: null)
{
}

/// <summary>
/// Initialize a new instance of the error struct with a specified error message and inner exception
/// </summary>
/// <param name="message">Error message</param>
/// <param name="exception">Inner exception</param>
public Error(string message, Exception exception)
: this(code: 0, message, exception: exception)
{
}
}
}
13 changes: 8 additions & 5 deletions Incendium.Result/Incendium.Result.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<Title>Incendium.Result</Title>
<Authors>mativ</Authors>
<Description>Provides a Result&lt;T&gt; type that allows a method to return both a value and an error in Rust style without exceptions</Description>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<Copyright>Copyright © Igor Matsak</Copyright>
</PropertyGroup>

Expand All @@ -32,14 +32,17 @@
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
76 changes: 76 additions & 0 deletions Incendium.Result/NullableResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;

namespace Incendium
{
/// <summary>
/// Represents a nullable result type, which can contain a nullable success value or an error value
/// </summary>
/// <typeparam name="T">Type of value</typeparam>
public readonly struct NullableResult<T>
{
/// <summary>
/// Gets nullable success value
/// </summary>
public T? Value { get; init; }
/// <summary>
/// Gets nullable error value
/// </summary>
public Error? Error { get; init; }

/// <summary>
/// Initialize a result instance from the success nullable value
/// </summary>
/// <param name="value">Nullable success value</param>
public NullableResult(T? value)
{
Value = value;
}

/// <summary>
/// Initialize a result instance from the error value
/// </summary>
/// <param name="error">Error</param>
public NullableResult(Error error)
{
Error = error;
}

/// <summary>
/// Initialize a result instance from the non-null error value with nullable type
/// </summary>
/// <param name="error">Non-null error value with nullable type</param>
/// <exception cref="ArgumentNullException"></exception>
public NullableResult(Error? error)
{
Error = error ?? throw new ArgumentNullException(nameof(error));
}

/// <summary>
/// Initialize a result instance from the success nullable value
/// </summary>
/// <param name="value">Nullable success value</param>
public static implicit operator NullableResult<T>(T? value) => new(value);
/// <summary>
/// Initialize a result instance from the error value
/// </summary>
/// <param name="error">Error</param>
public static implicit operator NullableResult<T>(Error error) => new(error);
/// <summary>
/// Initialize a result instance from the non-null error value with nullable type
/// </summary>
/// <param name="error">Non-null error value with nullable type</param>
/// <exception cref="ArgumentNullException"></exception>
public static implicit operator NullableResult<T>(Error? error) => new(error);

/// <summary>
/// Deconstruct struct to success value and error value
/// </summary>
/// <param name="value">Nullable success value</param>
/// <param name="error">Nullable error value</param>
public void Deconstruct(out T? value, out Error? error)
{
value = Value;
error = Error;
}
}
}
78 changes: 78 additions & 0 deletions Incendium.Result/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Incendium.Result
[![License: MIT](https://img.shields.io/github/license/matsakiv/incendium)](https://opensource.org/licenses/MIT) ![NuGet Version](https://img.shields.io/nuget/v/Incendium.Result) ![NuGet Downloads](https://img.shields.io/nuget/dt/Incendium.Result)

Incendium.Result is a small .NET Standard 2.1 library, which provides `Error`, `Result<T>` and `NullableResult<T>` useful types.

These types allow you to return success value or error from asynchronous and synchronous methods without explicit indication of the result type when returning and with convenient type deconstruction during processing the result.

These type also can be used for less error handling through exception mechanisms where possible.

## Getting started

### Installation

`PM> Install-Package Incendium.Result`

### Result`<T>`

If you need to return either a not-null value or an error from a method, you can use the `Result<T>` type:

```cs
public async Result<string> GetStringAsync() {
// ...
if (condition1) {
return "Test string result";
} else {
return new Error(code: 123, message: "Test error");
}

try {
// ...
} catch (Exception e) {
return new Error(code: 321, message: "Test error", exception: e);
}
}
```

Then processing the result might look like this:

```cs
var (str, error) = await GetStringAsync();

if (error != null) {
log.LogError(
error.Exception(),
"Error with code {@code} and message {@message}",
error.Code(),
error.Message());
}
```

The `Result<T>` instance can be created only from non-null value or from non-null error:

```cs
public Result<Foo> GetFooAsync() {
return new Foo(); // correct
return new Error(); // correct
return (Foo)null; // incorrect, CS8600 warning, throws ArgumentNullException
return (Foo)null!; // incorrect, throws ArgumentNullException
return (Foo?)null; // incorrect, CS8604 warning, throws ArgumentNullException
return (Foo?)null!; // incorrect, throws ArgumentNullException
return (Error?)null; // incorrect, throws ArgumentNullException
}
```

## NullableResult`<T>`

If the successful return value can be null, you must use the `NullableResult<T>` type:

```cs
public async NullableResult<Foo> GetFooAsync() {
return new Foo(); // correct
return new Error(); // correct
return (Foo?)null; // correct
return (Foo)null; // correct with CS8600 warning
return (Foo)null!; // correct
return (Error?)null; // incorrect, throws ArgumentNullException
}
```
91 changes: 48 additions & 43 deletions Incendium.Result/Result.cs
Original file line number Diff line number Diff line change
@@ -1,75 +1,80 @@
namespace Incendium
using System;

namespace Incendium
{
/// <summary>
/// Represents result type that can contain a not null success value or error
/// Represents a result type, which can contain a non-null success value or an error value
/// </summary>
/// <typeparam name="T">Type of value</typeparam>
public readonly struct Result<T>
{
/// <summary>
/// Not null success value
/// Gets a non-zero value if the error is null, otherwise gets null
/// </summary>
public T Value { get; init; }
public T Value { get; private init; }
/// <summary>
/// Error value
/// Gets nullable error value
/// </summary>
public Error? Error { get; init; }
public Error? Error { get; private init; }

/// <summary>
/// Initialize result instance from success value
/// </summary>
/// <param name="value">Value if success</param>
public static implicit operator Result<T>(T value) => new() { Value = value };
/// <summary>
/// Initialize result instance from error value
/// Initialize a result instance from the success non-null value
/// </summary>
/// <param name="error">Error value</param>
public static implicit operator Result<T>(Error error) => new() { Error = error };
/// <param name="value">Non-null success value</param>
/// <exception cref="ArgumentNullException"></exception>
public Result(T value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));

Value = value;
}

/// <summary>
/// Initialize result instance from nullable error value
/// Initialize a result instance from the error value
/// </summary>
/// <param name="error">Nullable error value</param>
public static implicit operator Result<T>(Error? error) => new() { Error = error };

public void Deconstruct(out T value, out Error? error)
/// <param name="error">Error</param>
public Result(Error error)
{
value = Value;
error = Error;
Value = default!;
Error = error;
}
}

/// <summary>
/// Represents result type that can contain a nullable success value or error
/// </summary>
/// <typeparam name="T"></typeparam>
public readonly struct NullableResult<T>
{
/// <summary>
/// Not null success value
/// Initialize a result instance from the non-null error value with nullable type
/// </summary>
public T? Value { get; init; }
/// <param name="error">Non-null error value with nullable type</param>
/// <exception cref="ArgumentNullException"></exception>
public Result(Error? error)
{
Value = default!;
Error = error ?? throw new ArgumentNullException(nameof(error));
}

/// <summary>
/// Error value
/// Initialize a result instance from the success non-null value
/// </summary>
public Error? Error { get; init; }

/// <param name="value">Non-null success value</param>
/// <exception cref="ArgumentNullException"></exception>
public static implicit operator Result<T>(T value) => new(value);
/// <summary>
/// Initialize result instance from success value
/// Initialize a result instance from the error value
/// </summary>
/// <param name="value">Value if success</param>
public static implicit operator NullableResult<T>(T value) => new() { Value = value };
/// <param name="error">Error</param>
public static implicit operator Result<T>(Error error) => new(error);
/// <summary>
/// Initialize result instance from error value
/// Initialize a result instance from the non-null error value with nullable type
/// </summary>
/// <param name="error">Error value</param>
public static implicit operator NullableResult<T>(Error error) => new() { Error = error };
/// <param name="error">Non-null error value with nullable type</param>
/// <exception cref="ArgumentNullException"></exception>
public static implicit operator Result<T>(Error? error) => new(error);

/// <summary>
/// Initialize result instance from nullable error value
/// Deconstruct struct to success value and error value
/// </summary>
/// <param name="value">Success value</param>
/// <param name="error">Nullable error value</param>
public static implicit operator NullableResult<T>(Error? error) => new() { Error = error };

public void Deconstruct(out T? value, out Error? error)
public void Deconstruct(out T value, out Error? error)
{
value = Value;
error = Error;
Expand Down
Loading

0 comments on commit db1d4f0

Please sign in to comment.