Results
Getting Started
Section titled “Getting Started”This guide installs the packages, models an error union, returns a result, and maps it onto an HTTP response.
Requirements
Section titled “Requirements”- .NET 11 SDK or later — the runtime packages target
net11.0; the source generator targetsnetstandard2.0so any compiler host can load it. - C# 15 preview — union declarations are a preview language feature, so a project that declares one needs
LangVersion=preview(the repository sets it centrally).
1. Reference the packages
Section titled “1. Reference the packages”dotnet add package Purview.Resultsdotnet add package Purview.Results.SourceGenerator # only when you model errors as a unionPurview.Results.SourceGenerator is a Roslyn component: add it as an ordinary PackageReference and it applies
to the compilation automatically. It also brings the analyzer, the CS0029 code fix, and the CA1815
suppression for opted-in unions.
2. Model the error as a union (optional)
Section titled “2. Model the error as a union (optional)”A C# 15 union makes the error cases strongly typed without giving up the single TError the result needs:
using Purview.Results;
[GenerateResult]public readonly union TenantError(TenantNotFound, TenantDisabled, TenantAlreadyExists);
public readonly record struct TenantNotFound(TenantId TenantId);public readonly record struct TenantDisabled(TenantId TenantId);public readonly record struct TenantAlreadyExists(TenantId TenantId);[GenerateResult] is generated by the source generator itself, so it needs no separate reference. For every case
the generator emits AsFailure<TValue>() in the union’s namespace:
public static class TenantErrorResultExtensions{ public static Result<TValue, TenantError> AsFailure<TValue>(this TenantNotFound error) => Result<TValue, TenantError>.Failure(error); // ... one overload per case type}See Union Errors for the modelling rules and Source Generator for the generated shape.
3. Return a result
Section titled “3. Return a result”using Purview.Results;
Result<Tenant, TenantError> GetTenant(TenantId tenantId) => _tenants.TryGetValue(tenantId, out var tenant) ? Result<Tenant, TenantError>.Success(tenant) : new TenantNotFound(tenantId).AsFailure<Tenant>();Result.Success<TValue, TError>(value) and Result.Failure<TValue, TError>(error) are the same factories
without repeating the value type. Both member states are also reachable through the result’s implicit
conversions, so a non-union error or a value can be returned directly:
Result<int, string> ok = 42;Result<int, string> failed = "not a number";4. Read the result
Section titled “4. Read the result”var result = GetTenant(tenantId);
if (result.IsSuccess) Console.WriteLine(result.Value.Name);
var message = result.Match( tenant => $"Found {tenant.Name}", error => $"Could not load the tenant: {error}");default is uninitialized: IsInitialized, IsSuccess and IsFailure are all false, and Value, Error,
Match, Map, Bind and MapError throw InvalidOperationException rather than guessing. See
Core Concepts and Combinators.
5. Map it onto HTTP
Section titled “5. Map it onto HTTP”dotnet add package Purview.Results.AspNetCorebuilder.Services.AddResultsHttp(options => options .Map<TenantNotFound>(error => TypedResults.NotFound()) .Map<TenantDisabled>(error => TypedResults.Problem(statusCode: StatusCodes.Status403Forbidden)) .Map<TenantError>(error => TypedResults.Problem(statusCode: StatusCodes.Status409Conflict)));
app.MapGet("/tenants/{id}", (string id) => GetTenant(new TenantId(id))).WithResultsHttp();A mapping for the case type wins, the mapping for the error type covers the remaining cases, and an
unmapped failure is a 500 that names the unmapped case, so a mapping gap is never silent. See
ASP.NET Core Integration.
6. Validate without exceptions
Section titled “6. Validate without exceptions”dotnet add package Purview.Results.ZodSharpZodSharp’s Validate already returns a ValidationResult<T>; ToResult turns that into a result whose error
type you choose:
return TenantInputSchema .Validate(input) .ToResult<TenantInput, TenantError>(errors => new TenantInputInvalid(input, errors));See ZodSharp Integration and, for ProblemDetails rendering, ZodSharp Problem Details.
Runnable examples
Section titled “Runnable examples”Every example is a non-packable project under src/examples, built on the same Tenancy domain:
dotnet run --project src/examples/Examples.Basicdotnet run --project src/examples/Examples.Zoddotnet run --project src/examples/Examples.AspNetCore --urls http://localhost:5215dotnet run --project src/examples/Examples.AspNetCore.Zod --urls http://localhost:5216Next steps
Section titled “Next steps”- Core Concepts — the three states and the throw-on-misuse contract.
- Combinators —
Match,Map,Bind,Ensure, probing, and the asynchronous forms. - Guarantees and Limitations — what the suite deliberately does not do.