Mitc.Support.Results 1.0.0

Mitc.Support.Results

Generic result types with monadic composition for explicit error handling. Services return results instead of throwing exceptions, making failure a first-class part of the method signature.

Core Types

Type Purpose
Result<TValue> Operation that returns a value or a string error
Outcome Operation that succeeds or fails with no return value
StructuredResult<TValue> Like Result<TValue> but with structured errors (ResultErrors)
Result<TValue, TError> Bring your own error type
Outcome<TError> Bring your own error type, no return value

All result types carry a ResultStatus that classifies the outcome:

public enum ResultStatus
{
    Success,
    NotFound,
    Invalid,
    Unauthorized,
    Conflict,
}

Basic Usage

Returning results from services

The Result static class provides shorthand factory methods. The method's return type drives the implicit conversion:

public async Task<Result<User>> GetByIdAsync(UserId id)
{
    var user = await context.Users.FindAsync(id);

    if (user is null)
        return Result.NotFound("User not found.");

    return user; // implicit Success
}

public async Task<Outcome> DeleteAsync(UserId id)
{
    var user = await context.Users.FirstOrDefaultAsync(u => u.Id == id);

    if (user is null)
        return Result.NotFound("User not found.");

    context.Users.Remove(user);
    await context.SaveChangesAsync();

    return Outcome.Success();
}

The same Result.NotFound(...) call works whether the method returns Result<User>, Outcome, or StructuredResult<User>.

Implicit conversions

For Result<TValue> where TValue is not string, implicit conversions keep service code clean:

Result<int> success = 42;           // implicit Success
Result<int> failure = "Not found."; // implicit Invalid
Outcome failure = "Bad request.";   // implicit Invalid

When TValue is string, use explicit factory methods to avoid ambiguity:

var result = Result<string>.Success("Alice");
var error = Result.NotFound("User not found.");

Match — handle both outcomes

// Returns a value
var response = result.Match(
    success: user => Ok(user),
    failure: error => NotFound(error));

// Outcome (no value)
var response = outcome.Match(
    success: () => NoContent(),
    failure: error => BadRequest(error));

Switch — side effects

result.Switch(
    success: user => logger.LogInformation("Found {User}", user.Name),
    failure: error => logger.LogWarning("Failed: {Error}", error));

Composition

Map — transform the success value

Result<string> userName = result.Map(user => user.Name);
// Success -> transformed value; Failure -> passes through unchanged

Bind — chain result-returning operations

Result<Order> order = await GetUser(id)
    .Bind(user => GetActiveOrder(user));
// Short-circuits on first failure

OnSuccess — side effect on success

result.OnSuccess(user => cache.Set(user.Id, user));
// Executes only on success, returns self for chaining

Async chaining

All composition methods work directly on Task<Result<T>>:

var name = await userService.GetByIdAsync(id)
    .Map(user => user.Name);

Structured Errors

When you need more than a string error — machine-readable codes, field-level validation errors — use StructuredResult<TValue> with ResultError:

public async Task<StructuredResult<User>> CreateAsync(CreateRequest request)
{
    if (await EmailExists(request.Email))
        return Result.Conflict(new ResultError("Already in use.", "Email", "DUPLICATE_EMAIL"));

    // ...
    return user;
}

ResultError constructors

new ResultError("Something went wrong.")                        // message only
new ResultError("Already in use.", "Email")                     // message + property
new ResultError("Already in use.", "Email", "DUPLICATE_EMAIL")  // message + property + code

Multiple errors

return Result.Invalid(ResultErrors.From(
    new ResultError("Name is required.", "Name"),
    new ResultError("Email is invalid.", "Email")));

ResultErrors helper factories

ResultErrors.Single("Something went wrong.")
ResultErrors.Single("Email", "Already in use.")
ResultErrors.Single(new ResultError("...", "Email", "DUPLICATE"))
ResultErrors.From(error1, error2, error3)

Failure Shorthand

The non-generic Result class returns lightweight Failure / StructuredFailure structs that implicitly convert to the method's return type:

// String errors — converts to Result<T>, Outcome, or StructuredResult<T>
Result.NotFound("User not found.")
Result.Invalid("Bad request.")
Result.Unauthorized("No access.")
Result.Conflict("Duplicate detected.")

// Structured errors — converts to StructuredResult<T>
Result.NotFound(new ResultError("Not found.", "Id", "NOT_FOUND"))
Result.Invalid(ResultErrors.From(error1, error2))

Custom Error Types

Use Result<TValue, TError> and Outcome<TError> with any error type:

Result<User, AppError> result = await GetUser(id);
Outcome<AppError> outcome = await DeleteUser(id);

Status Inspection

Every result exposes its status:

result.IsSuccess  // true if Status == ResultStatus.Success
result.Status     // the ResultStatus enum value
result.Value      // the success value (check IsSuccess first)
result.Error      // the error (check IsSuccess first)

Match is the recommended way to access values — it forces you to handle both cases.

Type Hierarchy

Result<TStatus, TValue, TError>         abstract base
├── Result<TValue, TError>              custom error type, ResultStatus
│   ├── Result<TValue>                  string errors
│   └── StructuredResult<TValue>        ResultErrors
└── Outcome<TError>                     no return value, custom error type
    └── Outcome                         no return value, string errors

Result (static)                          shorthand factory methods
├── returns Failure                      converts to Result<T>, Outcome, StructuredResult<T>
└── returns StructuredFailure            converts to StructuredResult<T>

Target Frameworks

netstandard2.0, net5.0, net6.0, net7.0, net8.0, net9.0, net10.0

Showing the top 20 packages that depend on Mitc.Support.Results.

Packages Downloads
Mitc.Integrations.Stripe
Turnkey Stripe integration for ASP.NET Core. Handles webhook processing, product/price catalog syncing, subscription management, checkout sessions, and metered usage reporting.
0

.NET 5.0

  • No dependencies.

.NET 6.0

  • No dependencies.

.NET 7.0

  • No dependencies.

.NET 8.0

  • No dependencies.

.NET 9.0

  • No dependencies.

.NET 10.0

  • No dependencies.

.NET Standard 2.0

  • No dependencies.

Version Downloads Last updated
1.1.0 0 4/13/2026
1.0.0 2 3/31/2026