Vyrn.Results 0.1.0

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package Vyrn.Results --version 0.1.0
                    
NuGet\Install-Package Vyrn.Results -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Vyrn.Results" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Vyrn.Results" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Vyrn.Results" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Vyrn.Results --version 0.1.0
                    
#r "nuget: Vyrn.Results, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Vyrn.Results@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Vyrn.Results&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Vyrn.Results&version=0.1.0
                    
Install as a Cake Tool

Vyrn.Results

Result pattern primitives for explicit success/failure flows — errors are values you return, not exceptions you throw. The library is designed so call sites read as naturally as (or better than) try/catch: one error factory works everywhere, pipelines compose without ceremony, and exceptions are captured at the boundary without losing diagnostics.

See docs/TECHNICAL.md for how everything works under the hood.

Install

dotnet add package Vyrn.Results

Requires .NET 10.

The 60-second tour

using Vyrn.Results;

// 1. Define your component's errors once, as a static class backed by an ErrorCatalog.
public static class OrderErrors
{
    private static readonly ErrorCatalog Catalog = new("Shop.Orders");

    public static ResultFailure NotFound(string id) => Catalog.ResourceNotFound("Order", id);
    public static ResultFailure GetFailed() => Catalog.Failure("GetFailed", "Failed to fetch order.");
}

// 2. Wrap throwing boundaries with Result.Try; return errors as values.
public Task<Result<OrderDto>> GetOrder(string id, CancellationToken ct)
    => Result.Try(
        async () =>
        {
            var response = await httpClient.GetAsync($"/api/orders/{id}", ct);

            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                return OrderErrors.NotFound(id);       // ResultFailure -> Result<OrderDto>
            }

            var dto = await response.Content.ReadFromJsonAsync<OrderDto>(ct);
            return Result.Success(dto!);
        },
        OrderErrors.GetFailed());                      // thrown exception -> this failure, exception attached

// 3. Compose without unwrapping.
public Task<Result<OrderResponse>> GetEnriched(string id, CancellationToken ct)
    => GetOrder(id, ct)
        .Bind(dto => LookupCustomer(dto, ct))
        .Map(customer => ToResponse(customer));

// 4. Collapse exactly once, at the edge.
var result = await service.GetEnriched(id, ct);
return result.ToActionResult(); // your app's mapping, e.g. to ProblemDetails

Core concepts

Type Role
Result / Result<T> The outcome: success (optionally with a value) or failure with an Error.
Error Stable Code, human Description, ErrorType (drives HTTP mapping), optional diagnostic Exception.
ResultFailure The error currency: a failure that is not yet a result. Implicitly converts to both Result and Result<T>.
ValidationError An Error aggregating multiple detail errors, so validation reports everything at once.
ErrorCatalog Mints component-scoped failures with a shared code prefix.
Errors Conventional factories: Required, NotFound, AlreadyExists.

The single most important idea is ResultFailure. Because your error factories return it, one call-site shape works for every method — the compiler converts it to whichever result type is needed. You never write Result.Failure<SomeLongType>(...) in application code.

Usage patterns

Boundaries: Result.Try

Wrap code that can throw (HTTP clients, serialization, drivers). Sync and async overloads exist:

Result.Try(async () => { ... }, OrderErrors.GetFailed());
  • The caught exception is attached to the returned error (Error.Exception) — log it at your transport boundary, once, instead of in every method.
  • OperationCanceledException is never swallowed; cancellation still flows.
  • Need per-exception handling? Use the lambda overload: Result.Try(action, ex => ...).
  • Type inference works even when the lambda mixes ResultFailure and Result.Success(x) returns — no explicit type argument needed.

Pipelines: Map, Bind, Ensure, Tap, Match

All exist on Result, Result<T>, and as extensions on Task<Result> / Task<Result<T>>, so async chains don't need intermediate awaits:

return inventoryClient.GetItems(ids, ct)              // Task<Result<List<Item>>>
    .Ensure(items => items.Count > 0, InventoryErrors.Empty())
    .Bind(items => orderClient.Create(items, ct))
    .Tap(created => cache.Store(created))
    .TapFailure(error => metrics.CountFailure(error.Code))
    .Map(created => ToResponse(created));
  • Map — transform the value (or produce one from a plain Result).
  • Bind — chain another result-returning operation; failures short-circuit.
  • Ensure — turn a success into a failure when a predicate rejects the value.
  • Tap / TapFailure — side effects that leave the result untouched.
  • Match — collapse to a final value with exactly one of two branches.

Guards and validation

var validation = Result.Validate(
    Result.Ensure(!string.IsNullOrEmpty(request.Title), OrderErrors.TitleRequired()),
    Result.Ensure(request.DeliveryDate > now, OrderErrors.DeliveryDateInPast()));

if (validation.IsFailure)
{
    return validation.AsFailure<OrderDto>();
}

Validate succeeds when all checks pass, returns a single failure unwrapped, and aggregates multiple failures into a ValidationError whose Details list every problem — so API consumers can fix all fields in one round trip.

Imperative access

Not everything wants to be a pipeline. Use TryGetValue or the properties:

if (!result.TryGetValue(out var order))
{
    return result.AsFailure<Response>(); // re-type the failure across value types
}

// Rust-style unwrapping:
var value = result.Unwrap();                          // Value; throws on failure
var orDefault = result.UnwrapOr(fallback);            // value or fallback
var orComputed = result.UnwrapOrElse(e => Fix(e));    // value or produced from the error

// or: result.IsSuccess / result.IsFailure / result.Error / result.Value

Value / Unwrap() on a failure throw InvalidOperationException by design — there is no silent default.

The transport edge

Map errors to your wire format in one place, keyed on ErrorType (Validation → 400, NotFound → 404, Conflict → 409, Failure → 500). Recommended shape is RFC 7807 ProblemDetails with the Code in extensions. That boundary is also the right single place to log Error.Exception. Never serialize Error itself — it's an internal type and carries the exception.

Gotchas

  • The implicit T -> Result<T> conversion takes a non-nullable T, so returning a possibly-null value through it is a nullability warning (an error under TreatWarningsAsErrors) — decide at the call site what null means and return an explicit component error. If a null slips past the annotations anyway, the runtime net converts it to a context-free General.Null failure rather than a success carrying null.
  • Error equality ignores the attached Exception — two errors with the same code, description and type are equal. This is what makes tests and deduplication sane.
  • A lambda that only throws (() => throw ...) is ambiguous between the sync and async Try overloads; give it an async modifier or an explicit return type. Real bodies are unaffected.

License

MIT — see LICENSE.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated