Olve.Results
0.38.0
dotnet add package Olve.Results --version 0.38.0
NuGet\Install-Package Olve.Results -Version 0.38.0
<PackageReference Include="Olve.Results" Version="0.38.0" />
<PackageVersion Include="Olve.Results" Version="0.38.0" />
<PackageReference Include="Olve.Results" />
paket add Olve.Results --version 0.38.0
#r "nuget: Olve.Results, 0.38.0"
#:package Olve.Results@0.38.0
#addin nuget:?package=Olve.Results&version=0.38.0
#tool nuget:?package=Olve.Results&version=0.38.0
Olve.Results
Olve.Results provides a lightweight, functional result type for structured, non-throwing error handling in .NET.
APIs return Result or Result<T> representing success or a collection of ResultProblems.
Installation
dotnet add package Olve.Results
Overview
This library provides predictable control flow and composable error propagation in codebases where exceptions are undesirable for routine failures.
| Type | Description |
|---|---|
Result |
Represents success or a collection of problems. |
Result<T> |
Represents success with a value or a collection of problems. |
DeletionResult |
Three-state result: success, not found, or error with problems. |
ResultProblem |
A single problem with message, optional exception, metadata, and origin info. |
ResultProblemCollection |
Immutable collection supporting merge, prepend, and append. |
Usage Examples
Basic result handling
Wrap operations in Result.Try() to convert exceptions into structured problems instead of throwing them.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L11-L18
Result<string> result = Result.Try<string, IOException>(
() => File.ReadAllText("/tmp/olve-results-readme-test.txt"));
if (result.TryPickProblems(out var problems, out var text))
{
foreach (var p in problems)
Console.WriteLine(p.Message);
}
This captures any thrown exceptions and converts them into ResultProblems without disrupting control flow.
Chaining dependent operations
Use Result.Chain() to execute dependent steps where the second operation relies on the result of the first.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L26-L32
Result<string> LoadUser(string name) => Result.Success(name);
Result<string> LoadProfile(string user) => Result.Success($"profile:{user}");
var result = Result.Chain(
() => LoadUser("Alice"),
user => LoadProfile(user)
);
If the first step fails, the chain stops immediately and returns its problems; otherwise, the next step runs with the successful value.
Combining independent operations
Use Result.Concat() when multiple operations can run independently, and you want to collect all problems together.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L40-L46
Result<string> LoadUser(string name) => Result.Success(name);
Result<int> LoadSettings() => Result.Success(42);
Result<(string user, int settings)> setup = Result.Concat(
() => LoadUser("current"),
() => LoadSettings()
);
If any operation fails, all problems are aggregated; otherwise, the combined success values are returned.
Adding context
Attach higher-level context to problems as they propagate upward for clear, hierarchical error traces.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L57-L60
var user = LoadUser("unknown");
if (user.TryPickProblems(out var problems))
{
var contextualized = problems.Prepend("User initialization failed");
Each layer can prepend its own message, building a human-readable error chain.
Validation pattern
Return Result from validation routines to make checks composable and explicit.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L72-L81
Result Validate(string? email)
{
if (string.IsNullOrWhiteSpace(email))
return new ResultProblem("Email is required");
if (!email.Contains('@'))
return new ResultProblem("Invalid email: {0}", email);
return Result.Success();
}
Working with result collections
ResultEnumerableExtensions provide helpers for aggregating problems or values from collections of results.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L95-L102
var results = new[]
{
LoadUser("Alice"),
LoadUser("Bob"),
LoadUser("Charlie"),
};
results.TryPickProblems(out var problems, out var users);
Other useful extensions:
HasProblems()— check if any result failedGetProblems()— flatten all problemsGetValues()— extract only successful values
Performance-oriented Try* pattern
For high-performance code paths, use a Try* method to avoid allocations entirely.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L112-L125
bool TryParseInt(
string input,
out int value,
[NotNullWhen(false)] out ResultProblem? problem)
{
if (!int.TryParse(input, out value))
{
problem = new ResultProblem("Failed to parse '{0}'", input);
return false;
}
problem = null;
return true;
}
This approach avoids constructing Result<T> entirely, ideal for inner loops.
ResultProblem
ResultProblem represents a single issue with optional metadata and automatically captures its origin (file + line number) for debugging.
Properties
Message,Args,ExceptionTags,Severity,SourceOriginInformation(automatically populated)
Example:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L139-L144
var problem = new ResultProblem("Database query failed: {0}", query)
{
Tags = ["database", "query"],
Severity = 2,
Source = "Repository",
};
Automatic origin capture provides traceable, stacktrace-like context without throwing exceptions.
DeletionResult
DeletionResult models deletion operations with three distinct outcomes: success, not found, and error.
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L154-L156
var success = DeletionResult.Success();
var notFound = DeletionResult.NotFound();
var error = DeletionResult.Error(new ResultProblem("disk full"));
Exhaustive matching
Use Match() to handle all three states explicitly:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L166-L171
var result = DeletionResult.NotFound();
var message = result.Match(
onSuccess: () => "Deleted",
onNotFound: () => "Already gone",
onProblems: problems => $"Error: {problems.First().Message}");
Converting to Result
MapToResult() converts a DeletionResult to a Result. By default, not-found is treated as success:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L179-L182
var result = DeletionResult.NotFound();
var asResult = result.MapToResult(allowNotFound: true); // Success
var strict = result.MapToResult(allowNotFound: false); // Failure
Result.Try
Result.Try<TException>() catches a specific exception type and converts it into a ResultProblem:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L203-L205
var result = Result.Try<IOException>(
() => File.ReadAllText("/nonexistent/path.txt"),
"Could not read config file");
Implicit conversions
A ResultProblem implicitly converts to Result, Result<T>, or DeletionResult, making failure returns concise:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L191-L193
Result result = new ResultProblem("not found");
Result<int> typed = new ResultProblem("parse error");
DeletionResult deletion = new ResultProblem("disk full");
Functional extensions
Map and Bind
Map transforms the value inside a successful Result<T>. Bind (flatMap) does the same but the mapping function itself returns a Result<T>:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L213-L220
var result = Result.Success("42");
var mapped = result.Map(s => s.Length); // Result<int> with value 2
var bound = result.Bind(s =>
int.TryParse(s, out var n)
? Result.Success(n)
: Result.Failure<int>(new ResultProblem("parse error")));
Other extensions:
ToEmptyResult()— discard the value, keeping only success/failure statusWithValueOnSuccess()— attach a value to a valuelessResult
Dictionary extensions
SetWithResult and GetWithResult wrap dictionary operations in results instead of throwing or returning booleans:
// ../../tests/Olve.Results.Tests/ReadmeDemo.cs#L229-L236
var dictionary = new Dictionary<string, int>();
var setResult = dictionary.SetWithResult("answer", 42); // Success
var duplicate = dictionary.SetWithResult("answer", 99); // Failure: key exists
IReadOnlyDictionary<string, int> readOnly = dictionary;
var getResult = readOnly.GetWithResult("answer"); // Result<int> with value 42
var missing = readOnly.GetWithResult("unknown"); // Failure: key not found
Design Notes
Goals
- Explicit success/failure flow without exceptions
- Immutable, structured problems with optional metadata
- Debuggability through automatic origin capture
Trade-offs
- Slight performance overhead from struct wrapping and collection allocations
- No typed errors — all failures use the generic
ResultProblemtype
These trade-offs prioritize clarity and composability over micro-optimizations or rigid typing.
Documentation
Full API reference: https://olivervea.github.io/Olve.Utilities/api/Olve.Results.html
License
MIT License © OliverVea
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Olve.Paths (>= 0.38.0)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Olve.Results:
| Package | Downloads |
|---|---|
|
Olve.Utilities
Various pieces of utility |
|
|
Olve.Operations
[Deprecated] Operation type implementation and utilities |
|
|
Olve.Results.TUnit
TUnit integration for Olve.Results |
|
|
Olve.Validation
Validation helpers built on top of Olve.Results |
|
|
Olve.MinimalApi
Minimal API extensions for ASP.NET Core including JSON conversion for IPath, result mapping, and validation helpers |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.38.0 | 553 | 2/19/2026 |
| 0.37.2 | 143 | 2/18/2026 |
| 0.37.1 | 139 | 2/18/2026 |
| 0.37.0 | 142 | 2/17/2026 |
| 0.36.1 | 417 | 11/15/2025 |
| 0.36.0 | 237 | 11/15/2025 |
| 0.35.2 | 232 | 11/9/2025 |
| 0.35.1 | 218 | 11/8/2025 |
| 0.35.0 | 209 | 11/8/2025 |
| 0.34.0 | 203 | 10/4/2025 |
| 0.33.0 | 202 | 9/13/2025 |
| 0.32.2 | 260 | 8/9/2025 |
| 0.32.1 | 247 | 8/9/2025 |
| 0.32.0 | 301 | 8/8/2025 |
| 0.31.0 | 345 | 8/5/2025 |
| 0.30.0 | 165 | 8/3/2025 |
| 0.29.1 | 167 | 8/3/2025 |
| 0.29.0 | 160 | 8/3/2025 |
| 0.28.2 | 146 | 8/2/2025 |
| 0.26.2 | 571 | 7/21/2025 |