DevJoy.Result
0.9.0
dotnet add package DevJoy.Result --version 0.9.0
NuGet\Install-Package DevJoy.Result -Version 0.9.0
<PackageReference Include="DevJoy.Result" Version="0.9.0" />
<PackageVersion Include="DevJoy.Result" Version="0.9.0" />
<PackageReference Include="DevJoy.Result" />
paket add DevJoy.Result --version 0.9.0
#r "nuget: DevJoy.Result, 0.9.0"
#:package DevJoy.Result@0.9.0
#addin nuget:?package=DevJoy.Result&version=0.9.0
#tool nuget:?package=DevJoy.Result&version=0.9.0
DevJoy.Result
A robust, lightweight Result pattern implementation for .NET that eliminates null reference exceptions and provides elegant error handling through functional programming principles.
๐ Features
- Type-Safe Error Handling: Replace exceptions with explicit Result types
- Railway Pattern: Chain operations safely without null checks
- Multiple Error Support: Collect and propagate multiple validation errors
- Monadic Operations: Functional programming patterns with
Then()
andTransform()
- Rich Error Context: Structured error codes with user and technical messages
- Zero Dependencies: Lightweight library with no external dependencies
๐ฆ Installation
dotnet add package DevJoy.Result
๐ฏ Quick Start
Creating Results
// Success case
var success = Result<string>.Success("Hello World");
// Single error
var failure = Result<string>.Failure(
new Error(ErrorCode.BadRequest, "Invalid input"));
// Multiple errors (validation)
var errors = new[]
{
new Error(ErrorCode.ParameterMissing, "Name is required"),
new Error(ErrorCode.ParameterOutOfRange, "Age must be positive")
};
var multipleErrors = Result<User>.Failure(errors);
Chaining Operations
var result = GetUser(userId) // Returns Result<User>
.Then(ValidateUser) // Chain validation (can fail)
.Transform(user => user.Name.ToUpper()) // Safe transformation
.Transform(name => $"Hello, {name}!") // Safe formatting
.Then(SaveGreeting); // Chain save operation (can fail)
// Check the final result
if (result.IsSuccess)
{
Console.WriteLine(result.Value);
}
else
{
foreach (var error in result.Errors)
{
Console.WriteLine($"Error: {error.UserMessage}");
}
}
๐๏ธ Core Concepts
Factory Methods
Method | Purpose | Use Case |
---|---|---|
Result<T>.Success(value) |
Create successful result | Operation completed successfully |
Result<T>.Failure(error) |
Create failed result | Single error occurred |
Result<T>.Failure(errors) |
Create failed result | Multiple validation errors |
Chaining Operations
Method | Purpose | When to Use |
---|---|---|
.Then(func) |
Chain operations that can fail | Database calls, API calls, validation |
.Transform(func) |
Transform values safely | Formatting, calculations, property access |
Properties
Property | Type | Purpose |
---|---|---|
IsSuccess |
bool |
Check if operation succeeded |
Value |
T |
Get the result value (only if successful) |
Errors |
IEnumerable<Error> |
Get all errors (empty if successful) |
๐ Common Patterns
API Controller Pattern
[HttpPost]
public IActionResult CreateUser(CreateUserRequest request)
{
var result = ValidateRequest(request) // Validate input
.Then(CreateUserInDatabase) // Database operation
.Transform(FormatResponse); // Format response
return result.IsSuccess
? Ok(result.Value)
: BadRequest(result.Errors.Select(e => e.UserMessage));
}
private Result<CreateUserRequest> ValidateRequest(CreateUserRequest request)
{
var errors = new List<Error>();
if (string.IsNullOrWhiteSpace(request.Name))
errors.Add(new Error(ErrorCode.ParameterMissing, "Name is required"));
if (request.Age < 0)
errors.Add(new Error(ErrorCode.ParameterOutOfRange, "Age must be positive"));
return errors.Any()
? Result<CreateUserRequest>.Failure(errors)
: Result<CreateUserRequest>.Success(request);
}
Data Processing Pipeline
public Result<ProcessedData> ProcessFile(string filePath)
{
return ReadFile(filePath) // Can fail - file I/O
.Transform(content => content.Trim()) // Safe - string operation
.Then(ParseJson) // Can fail - JSON parsing
.Transform(data => data.Records) // Safe - property access
.Then(ValidateRecords) // Can fail - validation
.Transform(FormatForOutput); // Safe - formatting
}
Service Layer with Error Propagation
public async Task<Result<User>> GetUserWithPermissionsAsync(int userId, string requesterRole)
{
return await GetUserById(userId) // Database call
.Then(user => CheckPermissions(user, requesterRole)) // Authorization
.Transform(user => HidePrivateFields(user)) // Data filtering
.Transform(user => AddComputedFields(user)); // Enhancement
}
๐จ Error Handling
Error Codes
The library provides comprehensive error codes for different scenarios:
// Client errors (4xx equivalent)
ErrorCode.BadRequest
ErrorCode.Unauthorized
ErrorCode.Forbidden
ErrorCode.ResourceNotFound
ErrorCode.ParameterMissing
ErrorCode.ParameterOutOfRange
ErrorCode.BusinessRuleViolation
// Server errors (5xx equivalent)
ErrorCode.ServerError
ErrorCode.DatabaseFailure
ErrorCode.ServiceUnavailable
ErrorCode.InfrastructureFailure
Creating Detailed Errors
// Basic error
var error = new Error(ErrorCode.BadRequest, "Invalid input");
// Error with technical details
var detailedError = new Error(
ErrorCode.DatabaseFailure,
"Unable to save user", // User message
"Connection timeout after 30s" // Technical message for logging
);
๐ง Advanced Usage
Custom Validation Functions
public static Result<User> ValidateUser(User user)
{
var errors = new List<Error>();
if (user.Age < 18)
errors.Add(new Error(ErrorCode.BusinessRuleViolation, "User must be 18 or older"));
if (user.Email?.Contains("@") != true)
errors.Add(new Error(ErrorCode.BadRequest, "Invalid email format"));
return errors.Any()
? Result<User>.Failure(errors)
: Result<User>.Success(user);
}
Conditional Operations
var result = GetUser(id)
.Then(user => user.IsActive
? Result<User>.Success(user)
: Result<User>.Failure(new Error(ErrorCode.BusinessRuleViolation, "User is inactive")))
.Transform(user => user.Name);
Type Transformations
// Transform between different types safely
Result<int> numberResult = Result<int>.Success(42);
Result<string> stringResult = numberResult
.Transform(n => n.ToString()); // int -> string
Result<bool> boolResult = stringResult
.Transform(s => s.Length > 0); // string -> bool
๐งช Testing
The library includes comprehensive tests demonstrating all usage patterns. Run tests with:
dotnet test
View the test file for detailed usage examples: ResultTests.cs
๐ค Why Use Result Pattern?
Before (Exception-based)
public User GetUser(int id)
{
try
{
var user = database.GetUser(id);
if (user == null) throw new NotFoundException("User not found");
ValidateUser(user); // Throws on validation failure
return ProcessUser(user); // Throws on processing failure
}
catch (Exception ex)
{
// Lost context, hard to handle multiple errors
throw;
}
}
After (Result pattern)
public Result<User> GetUser(int id)
{
return database.GetUser(id) // Returns Result<User>
.Then(ValidateUser) // Chain validation
.Then(ProcessUser); // Chain processing
// Errors are values, not exceptions
// Multiple errors preserved
// Type-safe, explicit error handling
}
Benefits
- โ Explicit: Errors are part of the method signature
- โ Composable: Chain operations elegantly
- โ Safe: No null reference exceptions
- โ Informative: Rich error context with codes and messages
- โ Testable: Errors are values, easy to test
- โ Performant: No exception throwing overhead
๐ License
Dual License Model:
- โ Free for personal, educational, and non-commercial use
- ๐ผ Commercial license required for business/commercial use
See LICENSE.md for full details.
For commercial licensing inquiries, please contact the author.
๐ Contributing
Contributions welcome! Please read our contributing guidelines and submit pull requests to the main
branch.
๐ Support
- Documentation: View tests for comprehensive examples
- Issues: Report bugs or request features via GitHub Issues
- Discussions: Ask questions in GitHub Discussions
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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. |
-
net8.0
- No dependencies.
-
net9.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 |
---|---|---|
0.9.0 | 66 | 8/22/2025 |
v0.9.0 - Release Candidate:
- Production-ready Result<T> pattern with comprehensive error handling
- Railway pattern operations: Then() for chainable error propagation
- Safe transformations: Transform() for value conversions without exceptions
- Rich Error system with structured ErrorCodes (400-500 series) and dual messages
- Multiple error accumulation for complex validation scenarios
- Real-world e-commerce examples: User โ Order โ Payment โ Email workflows
- 100 comprehensive tests covering success, failure, chaining, and error recovery
- Zero dependencies, multi-target support (.NET 8, 9)
- Battle-tested patterns from production applications