Darp.Results.Shouldly
1.2.0
dotnet add package Darp.Results.Shouldly --version 1.2.0
NuGet\Install-Package Darp.Results.Shouldly -Version 1.2.0
<PackageReference Include="Darp.Results.Shouldly" Version="1.2.0" />
<PackageVersion Include="Darp.Results.Shouldly" Version="1.2.0" />
<PackageReference Include="Darp.Results.Shouldly" />
paket add Darp.Results.Shouldly --version 1.2.0
#r "nuget: Darp.Results.Shouldly, 1.2.0"
#:package Darp.Results.Shouldly@1.2.0
#addin nuget:?package=Darp.Results.Shouldly&version=1.2.0
#tool nuget:?package=Darp.Results.Shouldly&version=1.2.0
Darp.Results
A Result type implementation for C# that provides a safe way to handle operations that can succeed or fail, inspired by Rust's Result<T, E>
type.
Packages
- Darp.Results - Core Result type implementation including Analyzers/CodeFixers
- Darp.Results.Shouldly - Shouldly extensions for Result testing
Quick Start
using Darp.Results;
// Create Results
Result<int, string> success = new Result.Ok<int, string>(42);
Result<int, string> failure = new Result.Err<int, string>("Something went wrong");
// Implicit conversions
Result<int, string> implicitSuccess = 42;
Result<int, string> implicitFailure = "error";
// Extract values safely
if (result.TryGetValue(out int value)) { /* use value */ }
if (result.TryGetError(out string error)) { /* handle error */ }
// Switch on result
return result switch
{
Result.Ok<int, string>(var value) => value,
Result.Err<int, string>(var error) => error,
}
Why another results library?
- Get values through switching
- API heavily inspired by Rust's Result<T, E>
- Ability to attach metadata to results
- Immutability
- Any object should be a possible error type (e.g., enums)
- Analyzers/CodeFixers
- Designed to benefit from the discriminated unions proposal
Existing projects did not fit all the requirements. However, these projects were an inspiration:
Core API Reference
Creation
Constructors / Implicit Conversions
// Value to Result
Result<int, string> result = 42; // Creates Ok(42)
Result<int, string> result = new Result.Ok<int, string>(42);
Result<int, string> result = new Result.Ok<int, string>(42, metadata: new Dictionary<string, object>());
// Error to Result
Result<int, string> result = "error"; // Creates Err("error")
Result<int, string> result = new Result.Err<int, string>("error");
Result<int, string> result = new Result.Err<int, string>("error", metadata: new Dictionary<string, object>());
Factory Methods
// Try-catch wrapper
Result<TValue, Exception> Result.Try<TValue>(Func<TValue> func)
// TryParse wrapper
Result<TValue, StandardError> Result.From<T, TValue>(T input, TryParseFunc<T, TValue> tryParse)
State Checking
bool IsOk { get; } // True if result is success
bool IsErr { get; } // True if result is error
Value Extraction
Safe Extraction
// Extract value
bool TryGetValue(out TValue value)
bool TryGetValue(out TValue value, out Result.Err<TNewValue, TError> error)
bool TryGetValue<TNewValue>(out TValue value, out Result.Err<TNewValue, TError> error)
// Extract error
bool TryGetError(out TError error)
bool TryGetError(out TError error, out Result.Ok<TValue, TNewError> success)
bool TryGetError<TNewError>(out TError error, out Result.Ok<TValue, TNewError> success)
// Extract with a default value
TValue Unwrap(TValue defaultValue) // Returns value or default
TValue Unwrap(Func<TValue> valueProvider) // Returns value or computed default
TValue? UnwrapOrDefault() // Returns value or default(TValue)
Unsafe Extraction (throws on wrong state)
TValue Unwrap() // Throws if error
TValue Expect(string message) // Throws with custom message if error
TError UnwrapError() // Throws if success
TError ExpectError(string message) // Throws with custom message if success
Transformations
Map Operations
// Transform success value
Result<TNewValue, TError> Map<TNewValue>(Func<TValue, TNewValue> func)
// Transform error
Result<TValue, TNewError> MapError<TNewError>(Func<TError, TNewError> func)
Logical Combinators
// And operations
Result<TNewValue, TError> And<TNewValue>(Result<TNewValue, TError> result)
Result<TValue, TError> And(Func<TValue, Result<TValue, TError>> resultProvider)
Result<TNewValue, TError> And<TNewValue>(Func<TValue, Result<TNewValue, TError>> resultProvider)
// Or operations
Result<TValue, TError> Or(Result<TValue, TError> result)
Result<TValue, TError> Or(Func<TError, Result<TValue, TError>> resultProvider)
Result<TValue, TNewError> Or<TNewError>(Func<TError, Result<TValue, TNewError>> resultProvider)
Utility Operations
Flattening
// Flatten nested Results
Result<TValue, TError> Flatten<TValue, TError>(Result<Result<TValue, TError>, TError> result)
Iteration
// Enumerate success values (yields single value for Ok, empty for Err)
IEnumerable<TValue> AsEnumerable()
IEnumerator<TValue> GetEnumerator() // Enables foreach
Metadata
// Add metadata
Result<TValue, TError> WithMetadata(string key, object value)
Result<TValue, TError> WithMetadata(ICollection<KeyValuePair<string, object>> metadata)
// Access metadata
IReadOnlyDictionary<string, object> Metadata { get; }
Testing with Shouldly
using Darp.Results.Shouldly;
// Assert success
result.ShouldBeSuccess();
result.ShouldHaveValue(expectedValue);
result.ShouldHaveValue(value => value.ShouldBeGreaterThan(0));
// Assert error
result.ShouldBeError();
result.ShouldHaveError(expectedError);
result.ShouldHaveError(error => error.ShouldNotBeNull());
Examples
Basic Usage
Divide two numbers, return a result and attempt to get the value
public Result<int, string> Divide(int a, int b)
{
if (b == 0)
return "Cannot divide by zero";
return a / b;
}
var result = Divide(10, 2);
if (result.TryGetValue(out int value))
{
Console.WriteLine($"Result: {value}");
}
Early returns
To achieve early returns, rust provides the ?
operator. Due to shortcomings of c#, we'll use the TryGet pattern to get the error
public Result<string, StandardError> WorkWithResult(Result<int, StandardError> result)
{
if (!result.TryGetValue(out int value, out Result.Err<string, StandardError>? err))
return err;
// ...
return value.ToString();
}
Chaining Operations
var result = Result.Ok<int, string>(10)
.Map(x => x * 2)
.And(x => x > 15 ? new Result.Ok<string, string>($"Big: {x}") : new Result.Err<string, string>("Too small"))
.Map(s => s.ToUpper());
result.ShouldHaveValue("BIG: 20");
Switching
Use switch expressions to easily access value/error
public Result<string, StandardError> SwitchOnResult(Result<int, StandardError> result)
{
return result switch
{
Result.Ok<int, StandardError>(var value, var metadata) => value.ToString(),
Result.Err<int, StandardError>(var error, var metadata) => error,
};
}
With Metadata
var result = Result.Ok<int, string>(42)
.WithMetadata("operation", "calculation")
.WithMetadata("timestamp", DateTime.UtcNow);
// Metadata is preserved through transformations
var transformed = result.Map(x => x.ToString());
// transformed.Metadata still contains the original metadata
License
This project is licensed under the Apache License 2.0.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Darp.Results (>= 1.2.0)
- Shouldly (>= 4.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.