eQuantic.Core.Outcomes.FluentValidation
                               
                            
                                2.0.0
                            
                        
                    dotnet add package eQuantic.Core.Outcomes.FluentValidation --version 2.0.0
NuGet\Install-Package eQuantic.Core.Outcomes.FluentValidation -Version 2.0.0
<PackageReference Include="eQuantic.Core.Outcomes.FluentValidation" Version="2.0.0" />
<PackageVersion Include="eQuantic.Core.Outcomes.FluentValidation" Version="2.0.0" />
<PackageReference Include="eQuantic.Core.Outcomes.FluentValidation" />
paket add eQuantic.Core.Outcomes.FluentValidation --version 2.0.0
#r "nuget: eQuantic.Core.Outcomes.FluentValidation, 2.0.0"
#:package eQuantic.Core.Outcomes.FluentValidation@2.0.0
#addin nuget:?package=eQuantic.Core.Outcomes.FluentValidation&version=2.0.0
#tool nuget:?package=eQuantic.Core.Outcomes.FluentValidation&version=2.0.0
eQuantic Core Outcomes Library
A modern, type-safe Result Pattern implementation for .NET with Railway-Oriented Programming support. Handle success and failure cases elegantly without exceptions.
๐ What's New in v2.0
- โ .NET 6 & .NET 8 support
- โ Immutable records for thread-safety
- โ Railway-Oriented Programming (Map, Bind, Match)
- โ Typed error system with categorization
- โ Full async/await support
- โ ASP.NET Core integration with Problem Details (RFC 7807)
- โ Result Combinators for aggregating multiple results
- โ Observability & Tracing with correlation IDs and execution timing
- โ Fluent API for better developer experience
- โ Comprehensive test coverage
๐ฆ Installation
dotnet add package eQuantic.Core.Outcomes
Or via Package Manager:
Install-Package eQuantic.Core.Outcomes
๐ Why Choose eQuantic.Core.Outcomes?
Unique Features (Not Available in Competing Libraries)
๐ Built-in Observability & Distributed Tracing
- โ CorrelationId & TraceId - Native OpenTelemetry, Jaeger, Zipkin integration
- โ
 ExecutionTime - Automatic performance measurement with Timed()/TimedAsync()
- โ Metadata - Rich contextual data for APM tools (Datadog, New Relic, Application Insights)
- ๐ฅ UNIQUE: Only Result library with native observability support for microservices!
๐ Most Complete Result Combinators
- โ
 Combine()- All-or-nothing aggregation
- โ
 Zip()- Type-safe combination of 2-4 results
- โ
 FirstSuccess()- Fallback/retry patterns
- โ
 SuccessfulValues()- Graceful degradation
- โ
 MergeErrors()- Comprehensive error collection
- โ
 Partition()- Success/failure separation
- ๐ฅ UNIQUE: 6 powerful combinators vs. 0-1 in other libraries!
โจ Seamless FluentValidation Integration
- โ
 Optional package: eQuantic.Core.Outcomes.FluentValidation
- โ
 .ToResult()automatic conversion
- โ
 .Validate()/.ValidateAsync()pipeline integration
- ๐ฅ UNIQUE: Only library with native FluentValidation support!
๐ Advanced ASP.NET Core Integration
- โ Automatic RFC 7807 Problem Details conversion
- โ Smart ErrorType โ HTTP Status mapping
- โ
 ToActionResult(),ToCreatedAtActionResult(),ToNoContentResult()
- โ REST API best practices built-in
โก Complete Async/Await Support
- โ
 All operations: MapAsync,BindAsync,MatchAsync,TapAsync,EnsureAsync
- โ Task unwrapping for cleaner pipelines
- โ
 CancellationTokensupport throughout
- โ
 Optimized with ConfigureAwait(false)
๐จ Rich Typed Error System
// 8 semantic error types with factory methods
Error.Validation()    // Validation failures
Error.NotFound()      // Resource not found
Error.Conflict()      // Resource conflicts
Error.Unauthorized()  // Authentication failures
Error.Forbidden()     // Permission denials
Error.BusinessRule()  // Domain rule violations
Error.Technical()     // Infrastructure failures
Error.External()      // Third-party failures
Comparison with Popular Libraries
| Feature | eQuantic.Outcomes | FluentResults | ErrorOr | Ardalis.Result | 
|---|---|---|---|---|
| Observability/APM | โ Built-in | โ None | โ None | โ None | 
| Result Combinators | โ 6 patterns | โ ๏ธ 1 basic | โ None | โ None | 
| FluentValidation | โ Native | โ Manual | โ Manual | โ Manual | 
| RFC 7807 Auto | โ Yes | โ ๏ธ Basic | โ No | โ ๏ธ Partial | 
| Async Support | โ Complete | โ ๏ธ Partial | โ Limited | โ ๏ธ Basic | 
| Railway-Oriented | โ Full | โ ๏ธ Partial | โ ๏ธ Partial | โ ๏ธ Partial | 
| Typed Errors | โ 8 types | โ ๏ธ Generic | โ ๏ธ Basic | โ ๏ธ Basic | 
| .NET 6/8 | โ Yes | โ Yes | โ Yes | โ Yes | 
| XML Docs | โ Complete | โ ๏ธ Basic | โ ๏ธ Basic | โ ๏ธ Basic | 
Our Advantage: We're the ONLY library combining observability, comprehensive combinators, FluentValidation integration, and full Railway-Oriented Programming in a modern, well-documented package.
๐ฏ Quick Start
Basic Usage
using eQuantic.Core.Outcomes;
using eQuantic.Core.Outcomes.Errors;
// Success case
var successResult = Result<int>.Success(42);
Console.WriteLine(successResult.Value); // 42
// Failure case
var error = new Error("USER_001", "User not found", ErrorType.NotFound);
var failureResult = Result<User>.Failure(error);
if (failureResult.IsFailure)
{
    Console.WriteLine(failureResult.FirstError.Message);
}
Implicit Conversions
// Implicitly convert from value to Result
Result<string> result = "Hello, World!";
// Implicitly convert from error to Result
Result<int> errorResult = new Error("ERR001", "Something went wrong");
๐ค๏ธ Railway-Oriented Programming
Chain operations elegantly with automatic short-circuiting on failures:
using eQuantic.Core.Outcomes.Extensions;
var result = GetUser(userId)
    .Map(user => user.Email)
    .Ensure(email => email.Contains("@"),
            Error.Validation("VAL001", "Invalid email format"))
    .Bind(email => SendWelcomeEmail(email))
    .Tap(email => _logger.LogInformation($"Email sent to {email}"));
return result.ToActionResult();
Map (Functor)
Transform the value inside a successful result:
Result<int> numberResult = Result<int>.Success(5);
Result<string> stringResult = numberResult.Map(n => n.ToString());
// Result: Success("5")
Bind (Monad / FlatMap)
Chain operations that return Results:
Result<User> GetUser(int id) { /* ... */ }
Result<Profile> GetProfile(User user) { /* ... */ }
var profileResult = GetUser(userId)
    .Bind(user => GetProfile(user));
Match (Pattern Matching)
Handle both success and failure cases:
var message = result.Match(
    onSuccess: user => $"Welcome, {user.Name}!",
    onFailure: errors => $"Error: {errors.First().Message}"
);
Ensure (Inline Validation)
Add validation inline in your chain:
var result = Result<int>.Success(5)
    .Ensure(n => n > 0, Error.Validation("VAL001", "Must be positive"))
    .Ensure(n => n < 100, Error.Validation("VAL002", "Must be less than 100"));
Tap (Side Effects)
Execute side effects without breaking the chain:
var result = GetUser(userId)
    .Tap(user => _logger.LogInformation($"User {user.Id} retrieved"))
    .TapError(errors => _logger.LogError($"Failed: {errors.First().Message}"))
    .Map(user => new UserDto(user));
โก Async Support
All operations have async variants:
using eQuantic.Core.Outcomes.Extensions;
var result = await GetUserAsync(userId)
    .MapAsync(user => TransformUserAsync(user))
    .BindAsync(user => ValidateUserAsync(user))
    .EnsureAsync(user => CheckPermissionsAsync(user),
                 Error.Forbidden("AUTH001", "Access denied"))
    .TapAsync(user => LogActivityAsync(user));
ToResultAsync
Wrap async operations with automatic exception handling:
var result = await _httpClient
    .GetStringAsync("https://api.example.com/data")
    .ToResultAsync();
if (result.IsSuccess)
{
    var data = result.Value;
}
๐จ Error Types
Create strongly-typed errors with semantic meaning:
// Validation errors
var error = Error.Validation("VAL001", "Email is required", "Email");
// Not found errors
var error = Error.NotFound("USER_001", "User not found", userId);
// Business rule errors
var error = Error.BusinessRule("BIZ001", "Cannot delete active subscription");
// Conflict errors
var error = Error.Conflict("CONF001", "Email already exists");
// Authorization errors
var error = Error.Unauthorized("AUTH001", "Invalid credentials");
var error = Error.Forbidden("AUTH002", "Insufficient permissions");
// Technical errors
var error = Error.Technical("TECH001", "Database connection failed", exception);
// External service errors
var error = Error.External("EXT001", "Payment gateway timeout", exception);
// From exceptions
var error = Error.FromException(exception, "ERR001", ErrorType.Technical);
Validation Errors
var validationError = new ValidationError(
    code: "VAL001",
    message: "Email format is invalid",
    propertyName: "Email",
    attemptedValue: "invalid-email"
);
๐ FluentValidation Integration
Optional Package: eQuantic.Core.Outcomes.FluentValidation
Seamlessly integrate FluentValidation with the Result Pattern:
Installation
dotnet add package eQuantic.Core.Outcomes.FluentValidation
Usage
using eQuantic.Core.Outcomes.FluentValidation;
using FluentValidation;
// Define your validator
public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
{
    public CreateUserRequestValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress();
        RuleFor(x => x.Age)
            .GreaterThanOrEqualTo(18);
    }
}
// Use directly with validator
var validator = new CreateUserRequestValidator();
var result = validator.Validate(request);
if (result.IsSuccess)
{
    // Process valid request
}
// Or chain with Railway-Oriented Programming
var createResult = Result<CreateUserRequest>.Success(request)
    .Validate(validator)
    .Bind(req => CreateUserAsync(req))
    .Map(user => new UserDto(user));
Convert ValidationResult to Result
var validationResult = validator.Validate(request);
// Convert to Result<T>
var result = validationResult.ToResult(request);
// Or non-generic Result
var result = validationResult.ToResult();
Async Validation
// Direct async validation
var result = await validator.ValidateAsync(request);
// Chain async validation
var finalResult = await Result<CreateUserRequest>.Success(request)
    .ValidateAsync(validator)
    .BindAsync(req => CreateUserAsync(req));
// Validate Task<Result<T>>
var result = await GetUserAsync(id)
    .ValidateAsync(validator);
Integration with Existing Results
var result = GetUser(userId)
    .Validate(userValidator)  // Validate if successful
    .Bind(user => GetProfile(user.Id))
    .Validate(profileValidator)  // Chain validations
    .Map(profile => new ProfileDto(profile));
Error Mapping
FluentValidation errors are automatically converted to ValidationError:
var result = validator.Validate(invalidRequest);
result.IsFailure.Should().BeTrue();
result.Errors.Should().AllBeOfType<ValidationError>();
var emailError = result.Errors
    .OfType<ValidationError>()
    .FirstOrDefault(e => e.PropertyName == "Email");
// ValidationError properties:
// - Code (from ErrorCode)
// - Message (from ErrorMessage)
// - PropertyName
// - AttemptedValue
๐ ASP.NET Core Integration
Automatic conversion to HTTP responses with Problem Details (RFC 7807):
using eQuantic.Core.Outcomes.AspNetCore;
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    var result = _userService.GetById(id);
    return result.ToActionResult();
}
// Automatic mapping:
// Success โ 200 OK
// NotFound โ 404 Not Found with ProblemDetails
// Validation โ 400 Bad Request with ValidationProblemDetails
// Unauthorized โ 401 Unauthorized
// Forbidden โ 403 Forbidden
// Conflict โ 409 Conflict
// BusinessRule โ 422 Unprocessable Entity
// Others โ 500 Internal Server Error
Custom Status Codes
// Custom success status code
return result.ToActionResult(201); // 201 Created
// Created at action
return result.ToCreatedAtActionResult("GetUser", new { id = user.Id });
// No content
return result.ToNoContentResult(); // 204 No Content
๐ Result Combinators
Combine and aggregate multiple Results efficiently:
Combine - All or Nothing
Combine multiple results into one. If all succeed, get all values. If any fails, get all errors:
using eQuantic.Core.Outcomes.Extensions;
// Validate multiple fields in parallel
var emailResult = ValidateEmail(request.Email);
var passwordResult = ValidatePassword(request.Password);
var ageResult = ValidateAge(request.Age);
var validationResult = ResultCombinators.Combine(
    emailResult,
    passwordResult,
    ageResult
);
if (validationResult.IsSuccess)
{
    var (email, password, age) = validationResult.Value;
    // All validations passed
}
else
{
    // Contains all validation errors
    return BadRequest(validationResult.Errors);
}
Zip - Combine Different Types
Combine results of different types into tuples:
var userResult = GetUser(userId);
var profileResult = GetProfile(userId);
// Combine into tuple
var combined = userResult.Zip(profileResult);
if (combined.IsSuccess)
{
    var (user, profile) = combined.Value;
    return new UserWithProfile(user, profile);
}
// Supports up to 4 results
var result = result1.Zip(result2, result3, result4);
FirstSuccess - Fallback Pattern
Try multiple sources, return first success:
// Try cache first, then database, then API
var result = ResultCombinators.FirstSuccess(
    GetFromCache(key),
    GetFromDatabase(key),
    GetFromApi(key)
);
// Returns first successful result
// If all fail, returns failure with all errors
SuccessfulValues - Partial Success
Get only successful values, ignore failures:
var processResults = items.Select(item => ProcessItem(item));
var successful = ResultCombinators.SuccessfulValues(processResults);
// Returns Result<IEnumerable<T>> with only successful values
// Useful when some failures are acceptable
Partition - Separate Successes and Failures
Split results into successful values and errors:
var results = items.Select(item => ValidateItem(item));
var (successes, errors) = results.Partition();
Console.WriteLine($"Processed: {successes.Count()}, Failed: {errors.Count()}");
MergeErrors - Collect All Errors
Useful for validation scenarios:
var validationResults = new[]
{
    ValidateField1(),
    ValidateField2(),
    ValidateField3()
};
var merged = ResultCombinators.MergeErrors(validationResults);
// Contains all validation errors from all failed results
๐ Observability & Tracing
Track and monitor your operations with built-in observability support:
Adding Correlation and Trace IDs
var result = await GetUserAsync(userId)
    .WithCorrelationId(httpContext.TraceIdentifier)
    .WithTraceId(Activity.Current?.Id)
    .WithMetadata("userId", userId)
    .WithMetadata("source", "api");
// Access observability data
Console.WriteLine($"CorrelationId: {result.CorrelationId}");
Console.WriteLine($"TraceId: {result.TraceId}");
Console.WriteLine($"Metadata: {result.Metadata["source"]}");
Measuring Execution Time
Automatically measure operation execution time:
// Synchronous
var result = ObservabilityExtensions.Timed(() =>
{
    return PerformExpensiveOperation();
});
Console.WriteLine($"Operation took: {result.ExecutionTime}");
// Asynchronous
var result = await ObservabilityExtensions.TimedAsync(async () =>
{
    return await PerformAsyncOperation();
});
Distributed Tracing Integration
Perfect for microservices and distributed systems:
public async Task<Result<Order>> ProcessOrderAsync(OrderRequest request, string correlationId)
{
    return await ObservabilityExtensions.TimedAsync(async () =>
    {
        var result = await _orderService.CreateOrderAsync(request);
        return result
            .WithCorrelationId(correlationId)
            .WithTraceId(Activity.Current?.Id)
            .WithMetadata("orderId", result.Value?.Id)
            .WithMetadata("service", "order-processing")
            .WithMetadata("environment", _env.EnvironmentName);
    });
}
Custom Metadata
Add any additional context to your results:
var result = Result<User>.Success(user)
    .WithMetadata("ipAddress", "192.168.1.1")
    .WithMetadata("userAgent", "Mozilla/5.0...")
    .WithMetadata("apiVersion", "2.0");
// Add multiple metadata entries at once
var metadata = new Dictionary<string, object>
{
    ["requestId"] = Guid.NewGuid(),
    ["timestamp"] = DateTimeOffset.UtcNow,
    ["region"] = "us-east-1"
};
result = result.WithMetadata(metadata);
๐ Legacy Support (v1.x API)
The v1.x Builder pattern is still available for backward compatibility:
var result = Outcome.FromItemResult<User>()
    .WithSuccess()
    .WithItem(user)
    .Result();
However, we recommend migrating to the new API for better type safety and functional programming support.
๐ Migration Guide (v1.x โ v2.0)
Before (v1.x)
var resultBuilder = Outcome.FromItemResult<User>();
try
{
    var user = await _repository.GetByIdAsync(id);
    if (user == null)
    {
        resultBuilder = resultBuilder
            .WithError()
            .WithStatus(ResultStatus.NotFound)
            .WithMessage("User not found");
        return NotFound(resultBuilder.Result());
    }
    resultBuilder = resultBuilder
        .WithSuccess()
        .WithItem(user);
    return Ok(resultBuilder.Result());
}
catch (Exception ex)
{
    resultBuilder = resultBuilder.WithException(ex);
    return StatusCode(500, resultBuilder.Result());
}
After (v2.0)
var result = await _repository
    .GetByIdAsync(id)
    .ToResultAsync()
    .Ensure(user => user != null,
            Error.NotFound("USER_001", "User not found", id.ToString()));
return result.ToActionResult();
๐ Advanced Examples
Combining Multiple Results
var userResult = GetUser(userId);
var profileResult = GetProfile(userId);
var combinedResult = userResult.Bind(user =>
    profileResult.Map(profile => new UserWithProfile(user, profile))
);
Conditional Chains
var result = GetUser(userId)
    .Ensure(user => user.IsActive,
            Error.BusinessRule("BIZ001", "User is not active"))
    .Ensure(user => user.EmailVerified,
            Error.BusinessRule("BIZ002", "Email not verified"))
    .Bind(user => ProcessUser(user));
Multiple Error Accumulation
var errors = new List<IError>();
if (string.IsNullOrEmpty(request.Email))
    errors.Add(Error.Validation("VAL001", "Email is required", "Email"));
if (request.Age < 18)
    errors.Add(Error.Validation("VAL002", "Must be 18 or older", "Age"));
if (errors.Any())
    return Result<User>.Failure(errors);
return Result<User>.Success(new User(request));
๐งช Testing
The library includes comprehensive test coverage. Example:
[Fact]
public void Map_OnSuccessResult_ShouldMapValue()
{
    // Arrange
    var result = Result<int>.Success(5);
    // Act
    var mappedResult = result.Map(x => x * 2);
    // Assert
    mappedResult.IsSuccess.Should().BeTrue();
    mappedResult.Value.Should().Be(10);
}
๐๏ธ Architecture
- Immutable by design - Results use C# records for immutability
- Type-safe - Compile-time guarantees with generic types
- Functional - Railway-Oriented Programming patterns
- Async-first - Full Task<Result<T>> support
- Zero allocations - Optimized for performance
- Nullable reference types - Full C# 8+ support
๐ Documentation
For more examples and detailed documentation, see:
๐ง Development
Building & Testing
# Restore dependencies
dotnet restore
# Build
dotnet build
# Run tests
dotnet test
Versioning
This project uses GitVersion for automatic semantic versioning:
- master: Stable releases (e.g., 2.0.0)
- develop: Preview releases (e.g., 2.1.0-alpha.5)
- feature/*: Feature branches (not published)
- release/*: Beta releases (e.g., 2.0.0-beta.1)
Use Conventional Commits to control version increments:
# Patch: 2.0.0 โ 2.0.1
git commit -m "fix: resolve memory leak"
# Minor: 2.0.0 โ 2.1.0
git commit -m "feat: add new combinator"
# Major: 2.0.0 โ 3.0.0
git commit -m "feat!: breaking API change"
Publishing
Packages are automatically published to NuGet.org via GitHub Actions:
- Push to master: Publishes stable version
- Push to develop: Publishes preview version
- Create tag (e.g., v2.0.1): Publishes specific version
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
Inspired by functional programming patterns from:
- Railway-Oriented Programming (Scott Wlaschin)
- Result types in Rust, F#, and Haskell
- FluentResults, ErrorOr, and other .NET libraries
eQuantic Systems ยฉ 2019-2025
| Product | Versions Compatible and additional computed target framework versions. | 
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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 was computed. 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. | 
- 
                                                    net6.0- eQuantic.Core.Outcomes (>= 2.0.0)
- FluentValidation (>= 11.9.0)
 
- 
                                                    net8.0- eQuantic.Core.Outcomes (>= 2.0.0)
- FluentValidation (>= 11.9.0)
 
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 | 
|---|---|---|
| 2.0.0 | 161 | 10/9/2025 | 
v2.0.0 - FluentValidation integration for Result Pattern