### XML Documentation Example Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Example of XML documentation comments for a public API method, including type parameters and return values. ```csharp /// /// Creates a successful rail carrying . /// /// The type of the success value. /// The success value to wrap. /// A containing the success value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rail Ok(T value) => value; ``` -------------------------------- ### Full Chain Example with Union Railway Operations Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html An example combining Tap, Bind, Map, and Recover operations to build a complex asynchronous operation with error handling and fallback logic. ```csharp // ── Full chain example ──────────────────────────────────────────────────── return await GetUserAsync(id) .TapAsync(u => auditLog.LogAccessAsync(u.Id)) .BindAsync(u => GetPermissionsAsync(u.Id)) .MapAsync(perms => new UserProfile(perms)) .RecoverAsync(_ => UserProfile.Guest) .ToHttpResultAsync(); ``` -------------------------------- ### xUnit Test Example Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Demonstrates a unit test following the Arrange, Act, Assert pattern for a successful rail creation. ```csharp [Fact] public void Ok_WithValue_ErrorIsNull() { // Arrange var value = 42; // Act Rail result = Union.Ok(value); // Assert Assert.Null(result.Error); Assert.True(result.TryGetValue(out var actual)); Assert.Equal(value, actual); } ``` -------------------------------- ### Installing UnionRailway Packages Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Command to add the necessary UnionRailway packages to a .NET project. ```bash dotnet add package UnionRailway dotnet add package UnionRailway.AspNetCore dotnet add package UnionRailway.EntityFrameworkCore # Optional ``` -------------------------------- ### C# Code Style Examples Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Illustrates preferred C# coding style for method implementations and async operations. ```csharp // ✅ Good public static Rail Ok(T value) => value; public static async ValueTask> MapAsync( this Rail result, Func> mapper) { ArgumentNullException.ThrowIfNull(mapper); // Implementation... } // ❌ Avoid public static Rail Ok(T value) { return value; } ``` -------------------------------- ### AddRailway() DI Setup with Options Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Register all UnionRailway services with a single call, optionally configuring global ProblemDetails enrichment. ```csharp builder.Services.AddRailway(options => { // Global ProblemDetails enrichment (applied to ALL error responses) options.ConfigureProblem = pd => pd.Extensions["traceId"] = Activity.Current?.Id; }); ``` -------------------------------- ### Copy Install Command to Clipboard Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html JavaScript function to copy the 'dotnet add package UnionRailway' command to the user's clipboard. Provides visual feedback upon successful copy. ```javascript function copyInstall(el) { const text = 'dotnet add package UnionRailway'; navigator.clipboard.writeText(text).then(() => { const hint = el.querySelector('.copy-hint'); const orig = hint.textContent; hint.textContent = 'copied!'; hint.style.color = 'var(--accent2)'; setTimeout(() => { hint.textContent = orig; hint.style.color = ''; }, 1800); }); } ``` -------------------------------- ### Fetch User with LanguageExt Error Handling Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Example of fetching a user by ID using LanguageExt's Fin type and pattern matching for error handling. ```csharp public async Task GetUser(int id) { Fin result = await userService.GetAsync(id); return result.Match( Succ: user => Results.Ok(user), Fail: error => error.Code switch { 404 => Results.NotFound(), 401 => Results.Unauthorized(), _ => Results.Problem(error.Message) } ); } ``` -------------------------------- ### UnionRailway Service Implementation Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Example of a service method returning `Rail`, demonstrating implicit conversion of a found user to a `Rail` and returning a specific `UnionError.NotFound`. ```csharp public async ValueTask> GetUserAsync(int id) { var user = await db.Users.FindAsync(id); if (user == null) return new UnionError.NotFound("User"); return user; // ✅ Implicit conversion } ``` -------------------------------- ### Global Exception Handling Setup Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Enable global exception handling for all endpoints by adding UseRailwayExceptionHandler early in the middleware pipeline. This ensures consistent RFC 7807 error responses. ```csharp var app = builder.Build(); app.UseRailwayExceptionHandler(); // Add early in pipeline app.UseAuthentication(); app.UseAuthorization(); ``` -------------------------------- ### Fetch User with ErrorOr Error Handling Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Example of fetching a user by ID using ErrorOr and its Match method for error handling. ```csharp public async Task GetUser(int id) { ErrorOr result = await userService.GetAsync(id); return result.Match( value => Results.Ok(value), errors => errors[0].Type switch { ErrorType.NotFound => Results.NotFound(), ErrorType.Unauthorized => Results.Unauthorized(), _ => Results.Problem(errors[0].Description) } ); } ``` -------------------------------- ### Fetch User with UnionRailway HTTP Integration Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Example of fetching a user by ID using UnionRailway, demonstrating its automatic RFC 7807 HTTP result mapping. ```csharp public async Task GetUser(int id) { var result = await userService.GetAsync(id); return result.ToHttpResult(); // ✅ Automatic RFC 7807 mapping } ``` -------------------------------- ### Custom Error Mapper Implementation Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Provides an example of a custom error mapper that implements `IUnionErrorMapper` to define specific HTTP responses for different error types. ```csharp // ── Option C: Custom error mapper ─────────────────────────────────────── public class CustomErrorMapper : IUnionErrorMapper { public IResult? TryMap(UnionError error) => error.Value switch { UnionError.NotFound nf => Results.Problem( detail: $"Could not locate '{nf.Resource}'.", statusCode: 404, title: "Resource Not Found"), UnionError.Custom { Code: "RATE_LIMIT" } c => Results.Problem( detail: c.Message, statusCode: 429, title: "Rate Limited"), _ => null // fall back to default RFC 7807 mapping }; } ``` -------------------------------- ### AddRailway() DI Setup with Custom Mapper Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Register UnionRailway services with a custom error mapper and optional configuration. ```csharp // Or with a custom mapper: builder.Services.AddRailway(options => ...); ``` -------------------------------- ### HttpClient GetFromJsonAsUnionAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Perform an HTTP GET request and automatically map the JSON response to a Rail type. Handles success (2xx) and error (4xx/5xx) responses. ```csharp var result = await httpClient.GetFromJsonAsUnionAsync("/users/42"); // Automatic mapping: 2xx → Success, 4xx/5xx → Error ``` -------------------------------- ### UnionRailway Endpoint Filter for Zero-Boilerplate APIs Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html This example shows how to use UnionRailway's endpoint filter to automatically handle `Rail` results. The `Rail` can be returned directly from the endpoint handler without manual conversion to `IResult`. ```csharp // Or zero-boilerplate with the endpoint filter: var api = app.MapGroup("/api").WithRailwayFilter(); api.MapGet("/users/{id}", async (int id, UserService svc) => await svc.GetUserAsync(id)); // Rail returned directly! ``` -------------------------------- ### Run Benchmarks Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Navigate to the benchmarks directory and run the performance benchmarks in release mode. ```bash cd tests/UnionRailway.Benchmarks dotnet run -c Release ``` -------------------------------- ### Restore and Build Solution Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Restore project dependencies and build the entire solution. ```bash dotnet restore dotnet build ``` -------------------------------- ### HTTP GET Request with Union Railway Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Performs an HTTP GET request and deserializes the JSON response into a UserDto, mapping common HTTP status codes to Union Railway error types. ```csharp // Install: dotnet add package UnionRailway.HttpClient // ── GET ────────────────────────────────────────────────────────────────── Rail result = await httpClient .GetFromJsonAsUnionAsync("/users/42"); // Automatic mapping: // 2xx → Rail with the deserialized body // 404 → UnionError.NotFound // 401 → UnionError.Unauthorized // 403 → UnionError.Forbidden // 409 → UnionError.Conflict // 422 → UnionError.Validation // 5xx → UnionError.SystemFailure ``` -------------------------------- ### Run Unit Tests Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Execute all unit tests in the solution. ```bash dotnet test ``` -------------------------------- ### Configuring Union Railway DI Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Demonstrates how to set up dependency injection for Union Railway, including global problem details enrichment and custom error mappers. ```csharp // One-line DI setup builder.Services.AddRailway(options => { // Global ProblemDetails enrichment — applied to ALL error responses options.ConfigureProblem = pd => pd.Extensions["traceId"] = Activity.Current?.Id; }); // Or with a custom mapper: builder.Services.AddRailway(); ``` -------------------------------- ### SystemFailure Constructor Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Illustrates creating SystemFailure errors with just a message or with both a message and an inner exception. ```csharp // Message only (wraps in InvalidOperationException internally): var sf = new UnionError.SystemFailure("Database connection lost"); // Message with inner exception: var sf = new UnionError.SystemFailure("Operation failed", innerException); ``` -------------------------------- ### Create Rail Success or Failure Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Demonstrates creating a Rail using implicit conversion from a ValueTask or explicit creation with Union.Ok or Union.Fail. ```csharp public ValueTask> GetUserAsync(int id) { // Option 1: Implicit conversion return user; // Option 2: Explicit creation return Union.Ok(user); return Union.Fail(new UnionError.NotFound("User")); } ``` -------------------------------- ### LanguageExt Approach to Handling Results Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Demonstrates how to handle a `Fin` result from LanguageExt using pattern matching to return an HTTP response. ```csharp using LanguageExt; using LanguageExt.Common; Fin result = await GetUserAsync(id); return result.Match( Succ: user => Results.Ok(user), Fail: error => Results.Problem(error.Message) ); ``` -------------------------------- ### Explicit Union Creation Helpers Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Shows how to explicitly create success and failure `Rail` instances using static helper methods. ```csharp var ok = Union.Ok(new User { Id = 1 }); var fail = Union.Fail(new UnionError.NotFound("User")); ``` -------------------------------- ### Benchmark Template Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Use this template when adding performance benchmarks for new operations. Ensure to include a descriptive name and memory diagnostics. ```csharp [Benchmark(Description = "Your operation")] [MemoryDiagnoser] public Rail YourOperation() { // Your code } ``` -------------------------------- ### Create Feature Branch Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Create a new branch for your feature development. ```bash git checkout -b feature/your-feature-name ``` -------------------------------- ### Pattern Matching Rail Results Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Illustrates different styles of pattern matching to handle the results of a Rail operation. The recommended style uses an early return with IsSuccess. ```csharp Rail result = await service.GetUserAsync(id); // Style A: IsSuccess out-parameter (early return, recommended) if (!result.IsSuccess(out var user, out var error)) return error.GetValueOrDefault().ToHttpResult(); Console.WriteLine(user.Name); // Style B: Match return result.Match( onOk: u => Results.Ok(u), onError: err => err.ToHttpResult()); // Style C: Error property check if (result.Error is not null) return result.Error.GetValueOrDefault().ToHttpResult(); ``` -------------------------------- ### Migration from LanguageExt to UnionRailway Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Shows how to migrate code from LanguageExt's Fin type to UnionRailway's Rail type, simplifying error handling. ```csharp // Before: LanguageExt Fin result = await repo.GetAsync(id); var user = result.Match( Succ: u => u, Fail: _ => throw new Exception() ); // After: UnionRailway Rail result = await repo.GetAsync(id); var user = result.Unwrap(); // or .Match() ``` -------------------------------- ### Clone Repository Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Clone your fork of the UnionRailway repository locally. ```bash git clone https://github.com/YOUR_USERNAME/unionrailway.git cd unionrailway ``` -------------------------------- ### Bind Operation for Chaining Operations Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Illustrates the Bind operation for chaining operations that may also fail. It sequentially executes operations, passing the successful result to the next operation. ```csharp // ── Bind: chain operations that may also fail ──────────────────────────── Rail summary = await GetUserAsync(id) // Rail .BindAsync(u => GetOrdersAsync(u.Id)) // Rail .MapAsync(orders => new OrderSummary(orders)); // Rail ``` -------------------------------- ### Run Tests with Coverage Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Execute tests and collect code coverage data using the specified collector. ```bash dotnet test --collect:"XPlat Code Coverage" ``` -------------------------------- ### Zero-Boilerplate Endpoints with .WithRailwayFilter() Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Demonstrates using `.WithRailwayFilter()` to automatically handle `Rail` results in ASP.NET Core endpoints, simplifying response mapping. ```csharp // ── Option B: Zero-boilerplate endpoint filter ─────────────────────────── var api = app.MapGroup("/api").WithRailwayFilter(); api.MapGet("/users/{id}", async (int id, UserService svc) => await svc.GetUserAsync(id)); // Rail → 200 / 404 auto api.MapDelete("/users/{id}", async (int id, UserService svc) => await svc.DeleteAsync(id)); // Rail → 204 auto ``` -------------------------------- ### Chaining HTTP Requests with BindAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Chains multiple HTTP requests using BindAsync. It first fetches a user and then fetches their orders, finally mapping the result to an OrderSummary. ```csharp // ── Chaining with the result ───────────────────────────────────────────── var summary = await httpClient .GetFromJsonAsUnionAsync("/users/42") .BindAsync(u => httpClient.GetFromJsonAsUnionAsync($"/orders?userId={u.Id}")) .MapAsync(orders => new OrderSummary(orders.Length)); return summary.ToHttpResult(); ``` -------------------------------- ### Pattern Matching on Rail Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Illustrates three styles of pattern matching on a Rail result: using IsSuccess, Match, or checking the Error property. ```csharp // Style 1: IsSuccess pattern if (!result.IsSuccess(out var user, out var error)) return error.GetValueOrDefault().ToHttpResult(); // Style 2: Match return result.Match( onOk: user => Results.Ok(user), onError: error => error.ToHttpResult()); // Style 3: Error property check if (result.Error is not null) return result.Error.GetValueOrDefault().ToHttpResult(); ``` -------------------------------- ### Returning Rail from Service Methods Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Demonstrates how to return Rail from service methods, handling both successful results and explicit errors. Implicit conversion to Rail is supported for success types. ```csharp public class UserService { public async ValueTask> GetUserAsync(int id) { var user = await db.Users.FindAsync(id); if (user == null) return new UnionError.NotFound("User"); // explicit error return user; // implicit conversion ✅ } public async ValueTask> DeleteUserAsync(int id) { var user = await db.Users.FindAsync(id); if (user == null) return new UnionError.NotFound("User"); db.Users.Remove(user); await db.SaveChangesAsync(); return Unit.Value; // 204 No Content } } ``` -------------------------------- ### Simplified Service Method with UnionRailway Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html This service method uses UnionRailway's `FirstOrDefaultAsUnionAsync` to fetch a user, returning a `Rail` type. This eliminates the need for explicit null checks and exception handling within the service. ```csharp // Service method — one line public async ValueTask> GetUserAsync(int id) => await db.Users .FirstOrDefaultAsUnionAsync("User", x => x.Id == id); ``` -------------------------------- ### UnionRailway vs. Exceptions Comparison Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Highlights the advantages of UnionRailway over traditional exception handling by showing explicit type safety and performance benefits. ```csharp // ❌ Exceptions: Slow, unclear, unsafe try { var user = await repo.GetAsync(id); } catch (NotFoundException) { return Results.NotFound(); } catch (UnauthorizedException) { return Results.Unauthorized(); } // What other exceptions can this throw? 🤷 // ✅ UnionRailway: Fast, explicit, type-safe var result = await repo.GetAsync(id); return result.ToHttpResult(); // Compiler knows: Success | NotFound | Unauthorized ✅ ``` -------------------------------- ### FluentResults Approach to Handling Results Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Demonstrates handling a `Result` from FluentResults by checking for failure and returning an appropriate HTTP response. ```csharp using FluentResults; Result result = await GetUserAsync(id); if (result.IsFailed) { var error = result.Errors.First(); return Results.Problem(error.Message); } return Results.Ok(result.Value); ``` -------------------------------- ### EF Core SaveChangesAsUnionAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Placeholder for demonstrating the `SaveChangesAsUnionAsync` method from Entity Framework Core integration. ```csharp // ── SaveChangesAsUnionAsync ────────────────────────────────────────────── ``` -------------------------------- ### OneOf Approach to Handling Discriminated Unions Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Shows how to handle a `OneOf` result from OneOf by matching on the possible types to return an HTTP response. ```csharp using OneOf; OneOf result = await GetUserAsync(id); return result.Match( user => Results.Ok(user), notFound => Results.NotFound(), serverError => Results.Problem(serverError.Message) ); ``` -------------------------------- ### Customizing ProblemDetails Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Post-process ProblemDetails responses to add trace IDs or strip details in production. ```csharp return result.ToHttpResult(configureProblem: pd => { pd.Extensions["traceId"] = Activity.Current?.Id; if (env.IsProduction()) pd.Detail = "An error occurred."; }); ``` -------------------------------- ### Switch - Void Match for Side Effects Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Demonstrates using Switch for executing side effects on success or error branches without returning a value, including an async version. ```csharp result.Switch( onOk: user => logger.LogInformation("Found user {Id}", user.Id), onError: err => logger.LogWarning("Failed: {Error}", err)); // Async version: await resultTask.SwitchAsync( onOk: user => { logger.LogInformation("Found user {Id}", user.Id); return Task.CompletedTask; }, onError: err => { logger.LogWarning("Failed: {Error}", err); return Task.CompletedTask; }); ``` -------------------------------- ### Current UnionRailway Implementation Structure Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Illustrates the current struct-based implementation of Rail and UnionError for .NET 8 and .NET 11 Preview. ```csharp [Union] // High-performance struct-based implementation public readonly struct Rail { /* ... */ } public readonly struct UnionError { /* ... */ } ``` -------------------------------- ### UnionRailway Error Handling for Services Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Demonstrates how to use UnionRailway to return a type-safe `Rail` from a service method, replacing exceptions with explicit return types. ```csharp public async ValueTask> GetUserAsync(int id) { return await db.Users.FirstOrDefaultAsUnionAsync("User", x => x.Id == id); } ``` -------------------------------- ### Wrapping Legacy Code with Exception Handling Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Wraps legacy code that might throw exceptions using UnionWrapper.RunAsync, converting any exception into a UnionError.SystemFailure. ```csharp // ── Wrap exception-based legacy code ───────────────────────────────────── Rail result = await UnionWrapper.RunAsync( () => legacyReportService.GenerateAsync(reportId)); // Any exception → UnionError.SystemFailure(ex) ``` -------------------------------- ### Use Union.Ok(object) for Explicit Boxing Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md When boxing to object, use the explicit Union.Ok(object) overload to prevent potential type inference issues. ```csharp // ✅ GOOD: Explicit object overload prevents inference issues Rail result = Union.Ok(new { Id = 1, Name = "Test" }); ``` -------------------------------- ### Synchronous and Asynchronous Railway Composition Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Shows how to chain operations using Map for transformation and Bind for chaining dependent operations, both synchronously and asynchronously. ```csharp // Sync var result = Union.Ok(5) .Map(x => x * 2) // Transform success .Bind(x => ValidateAsync(x)); // Chain operations // Async var result = await GetUserAsync(id) .BindAsync(user => GetOrdersAsync(user.Id)) .MapAsync(orders => orders.Count) .ToHttpResultAsync(); ``` -------------------------------- ### Migration from ErrorOr to UnionRailway Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Demonstrates migrating from ErrorOr to UnionRailway, focusing on the change in success/error checking and value retrieval. ```csharp // Before: ErrorOr ErrorOr result = await repo.GetAsync(id); if (result.IsError) return result.Errors[0]; // After: UnionRailway Rail result = await repo.GetAsync(id); if (!result.IsSuccess(out var user, out var error)) return error.GetValueOrDefault(); ``` -------------------------------- ### Key Performance Wins in UnionRailway Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Highlights the main performance advantages of UnionRailway, such as zero-allocation success paths and efficient async operations. ```text ✅ **Zero-allocation success path** - struct-based Rail lives on stack ✅ **Inline operations** - Map/Bind with AggressiveInlining ✅ **ValueTask async** - 48-100ns overhead vs Task's 200+ns ✅ **Minimal error cost** - 24B for simple errors, 464B for validation ``` -------------------------------- ### Endpoint Error Handling with .ToHttpResult() Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Illustrates how to use the `.ToHttpResult()` extension method on `Rail` to convert results into HTTP responses for individual endpoints. ```csharp // ── Option A: .ToHttpResult() per endpoint ────────────────────────────── app.MapGet("/users/{id:int}", async (int id, UserService svc) => (await svc.GetUserAsync(id)).ToHttpResult()); // POST: returns 201 Created with Location header app.MapPost("/users", async (CreateUserRequest req, UserService svc) => (await svc.CreateAsync(req)).ToHttpResult(createdUri: $"/users/{req.Id}")) .WithCreatedRailOpenApi(); // DELETE: Rail → 204 No Content automatically app.MapDelete("/users/{id}", async (int id, UserService svc) => (await svc.DeleteAsync(id)).ToHttpResult()); // Inline ProblemDetails customisation return result.ToHttpResult(configureProblem: pd => { pd.Extensions["traceId"] = Activity.Current?.Id; if (env.IsProduction()) pd.Detail = "An error occurred."; }); ``` -------------------------------- ### Map Operation on Rail Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Demonstrates the Map operation on a Rail object. It transforms the successful value if the Rail is Ok, otherwise, it remains a Fail. ```csharp // ── Map: transform a successful value ──────────────────────────────────── Rail doubled = Union.Ok(5).Map(x => x * 2); // Ok(10) Rail skipped = Union.Fail(err).Map(x => x * 2); // still Fail ``` -------------------------------- ### UnionRailway Approach with HTTP Integration Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Demonstrates UnionRailway's approach to handling results and integrating with HTTP responses, utilizing automatic RFC 7807 support. ```csharp using UnionRailway; using UnionRailway.AspNetCore; var result = await GetUserAsync(id); return result.ToHttpResult(); ``` -------------------------------- ### Traditional Exception-Based Error Handling Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Illustrates a common C# pattern using try-catch blocks for error handling, which can be slow and lacks type safety. ```csharp public async Task GetUser(int id) { try { var user = await db.Users.FindAsync(id); if (user == null) return Results.NotFound(); return Results.Ok(user); } catch (UnauthorizedAccessException) { return Results.Unauthorized(); } catch (Exception ex) { logger.LogError(ex, "Failed to get user"); return Results.Problem("Something went wrong"); } } ``` -------------------------------- ### Use ValueTask> for Async Methods Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Employ ValueTask> for asynchronous methods, especially for hot paths, to achieve zero allocation on cached results. ```csharp // ✅ GOOD: Zero allocation on cached results public async ValueTask> GetUserAsync(int id) { var user = await db.Users.FindAsync(id); return user ?? new UnionError.NotFound("User"); } ``` -------------------------------- ### ToFail - Shorthand Error Creation Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Shows how to create a failed Rail directly from a UnionError instance using the ToFail extension method. ```csharp // Before: return Union.Fail(new UnionError.Conflict("duplicate")); // After: UnionError error = new UnionError.Conflict("duplicate"); return error.ToFail(); ``` -------------------------------- ### Wrapping Legacy Exception-Based Code Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Use UnionWrapper.RunAsync to safely execute legacy code that throws exceptions, converting them into UnionRailway results. ```csharp // Wrap legacy exception-based code: var result = await UnionWrapper.RunAsync(() => legacyService.LoadAsync()); ``` -------------------------------- ### Global Exception Middleware Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Shows how to add the global exception middleware to the ASP.NET Core pipeline to handle errors automatically. ```csharp // Global exception middleware (add early in pipeline) app.UseRailwayExceptionHandler(); ``` -------------------------------- ### Ensure - Guarding Values in the Chain Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Demonstrates using EnsureAsync to validate a success value against a predicate, short-circuiting to an error if the validation fails. ```csharp var result = await GetTransactionAsync(id) .EnsureAsync( t => t is not null, _ => new UnionError.NotFound("Transaction")) .BindAsync(async t => { t.Items.ForEach(i => i.Issued = true); return await SaveAsync(t); }); // If GetTransactionAsync returns Ok(null), Ensure converts it to NotFound // and BindAsync is never called. ``` -------------------------------- ### LanguageExt Error Handling Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Handles results using LanguageExt's Fin type. Requires manual mapping of HTTP codes to Results.NotFound, Results.Unauthorized, or Results.Problem. ```csharp Fin result = await svc.GetAsync(id); return result.Match( Succ: user => Results.Ok(user), Fail: error => error.Code switch { 404 => Results.NotFound(), 401 => Results.Unauthorized(), _ => Results.Problem(error.Message) } ); // Must know HTTP codes. No RFC 7807. ``` -------------------------------- ### ErrorOr Approach to Handling Results Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Illustrates handling an `ErrorOr` result from ErrorOr by matching on the value or errors and constructing an appropriate HTTP response. ```csharp using ErrorOr; ErrorOr result = await GetUserAsync(id); return result.Match( value => Results.Ok(value), errors => Results.Problem(string.Join(", ", errors.Select(e => e.Description))) ); ``` -------------------------------- ### Wrapping Legacy Code with Nullability Handling Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Wraps legacy code that returns nullable types using UnionWrapper.RunNullableAsync. Converts null results into a UnionError.NotFound. ```csharp // ── Wrap nullable-returning code ───────────────────────────────────────── Rail maybeUser = await UnionWrapper.RunNullableAsync( () => legacyRepo.FindByEmailAsync(email)); // null → UnionError.NotFound("value") ``` -------------------------------- ### Simplified HTTP Endpoint with UnionRailway Filter Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Demonstrates using `.WithRailwayFilter()` on an API group to automatically handle `Rail` results without needing explicit `.ToHttpResult()` calls in each endpoint. ```csharp var api = app.MapGroup("/api").WithRailwayFilter(); api.MapGet("/users/{id}", async (int id, UserService svc) => await svc.GetUserAsync(id)); // Returns Rail directly! ``` -------------------------------- ### UnionRailway vs. ErrorOr/LanguageExt Comparison Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Compares UnionRailway with other result-based libraries like ErrorOr and LanguageExt, emphasizing UnionRailway's automatic RFC 7807 integration and reduced manual mapping. ```csharp // ❌ Other libraries: Manual mapping, no ecosystem ErrorOr result = await repo.GetAsync(id); return result.Match( value => Results.Ok(value), errors => errors[0].Type switch { ErrorType.NotFound => Results.NotFound(), ErrorType.Unauthorized => Results.Unauthorized(), _ => Results.Problem() // 😰 Manual everywhere }); // ✅ UnionRailway: Automatic RFC 7807, built-in integrations var result = await repo.GetAsync(id); return result.ToHttpResult(); // ✨ Done! ``` -------------------------------- ### Chain Operations for Readability Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Compose operations using chaining for improved readability in a railway-style manner. ```csharp // ✅ GOOD: Railway-style composition var result = await GetProductAsync(id) .BindAsync(product => ValidateStockAsync(product)) .MapAsync(product => new ProductDto(product)) .ToHttpResultAsync(); ``` -------------------------------- ### EF Core FirstOrDefaultAsUnionAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Shows how to use `FirstOrDefaultAsUnionAsync` from Entity Framework Core integration to replace manual null checks and NotFound returns. ```csharp // Install: dotnet add package UnionRailway.EntityFrameworkCore // ── FirstOrDefaultAsUnionAsync ─────────────────────────────────────────── // Replaces: var user = await db.Users.FirstOrDefaultAsync(...); // if (user == null) return NotFound(); Rail user = await db.Users .FirstOrDefaultAsUnionAsync("User", x => x.Id == id); // ✅ Returns NotFound("User") automatically when null // With cancellation token Rail user = await db.Users .FirstOrDefaultAsUnionAsync("User", x => x.Id == id, cancellationToken); ``` -------------------------------- ### Product Service with Union Railway Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html A ProductService demonstrating fetching and updating products using Union Railway for error handling. It uses FirstOrDefaultAsUnionAsync for fetching and SaveChangesAsUnionAsync for updates. ```csharp // ── Full service example ───────────────────────────────────────────────── public class ProductService(AppDbContext db) { public async ValueTask> GetProductAsync(int id) => await db.Products.FirstOrDefaultAsUnionAsync("Product", p => p.Id == id); public async ValueTask> UpdateProductAsync(int id, UpdateDto dto) { var product = await db.Products.FirstOrDefaultAsUnionAsync("Product", p => p.Id == id); if (!product.IsSuccess(out var p, out var err)) return err.GetValueOrDefault(); p.Name = dto.Name; p.Price = dto.Price; var saved = await db.SaveChangesAsUnionAsync(); return saved.IsSuccess(out _, out var saveErr) ? Unit.Value : saveErr.GetValueOrDefault(); } } ``` -------------------------------- ### Traditional Error Handling in ASP.NET Core Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html This snippet demonstrates a typical ASP.NET Core endpoint using try-catch blocks for error handling. It is prone to inconsistent error shapes and relies on exceptions for control flow. ```csharp public async Task GetUser(int id) { try { var user = await db.Users.FindAsync(id); if (user == null) return Results.NotFound(); return Results.Ok(user); } catch (UnauthorizedAccessException) { return Results.Unauthorized(); } catch (Exception ex) { logger.LogError(ex, "Failed"); return Results.Problem("Something went wrong"); } } ``` -------------------------------- ### Custom Error Mapper Implementation Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Implement IUnionErrorMapper for full control over error to HTTP response translation. When TryMap returns null, the default RFC 7807 mapping is used. ```csharp public class CustomErrorMapper : IUnionErrorMapper { public IResult? TryMap(UnionError error) => error.Value switch { UnionError.NotFound nf => Results.Problem( detail: $"We could not locate '{nf.Resource}'.", statusCode: 404, title: "Resource Not Found"), UnionError.Custom { Code: "RATE_LIMIT" } c => Results.Problem( detail: c.Message, statusCode: 429, title: "Rate Limited"), _ => null // fall back to default mapping }; } // Registration: builder.Services.AddSingleton(); // Usage (inject via DI or pass directly): return result.ToHttpResult(errorMapper: mapper); ``` -------------------------------- ### UnionRailway Approach with Rail Type Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Illustrates UnionRailway's consistent use of the `Rail` type for results and its integration with HTTP responses. ```csharp using UnionRailway; using UnionRailway.AspNetCore; Rail result = await GetUserAsync(id); return result.ToHttpResult(); ``` -------------------------------- ### Recovering from Specific Errors with Fallback Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Use RecoverAsync to handle specific error types, providing a fallback value if the specified error occurs. ```csharp // Recover from specific errors with a fallback var user = await GetUserAsync(id) .RecoverAsync(_ => guestUser); ``` -------------------------------- ### ASP.NET Core Endpoint Integration Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Shows how to integrate UnionRailway results into ASP.NET Core endpoints using ToHttpResult for various HTTP methods and response types. ```csharp app.MapGet("/users/{id:int}", async (int id, UserService service) => (await service.GetUserAsync(id)).ToHttpResult()); app.MapPost("/users", async (CreateUserRequest req, UserService service) => (await service.CreateAsync(req)).ToHttpResult(createdUri: $"/users/{id}")) .WithCreatedRailOpenApi(); // Rail automatically returns 204 No Content app.MapDelete("/users/{id}", async (int id, UserService service) => (await service.DeleteAsync(id)).ToHttpResult()); ``` -------------------------------- ### Pull Request Description Template Source: https://github.com/salihcantekin/unionrailway/blob/main/CONTRIBUTING.md Provides a template for pull request descriptions, covering the purpose, changes, testing, and breaking changes. ```markdown ## Description Brief description of what this PR does. ## Changes - List of changes made - Another change ## Testing How has this been tested? ## Breaking Changes Are there any breaking changes? If so, what and why? ``` -------------------------------- ### UnionRailway Approach to Handling Results Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Shows the concise way to handle a result from UnionRailway by converting it directly to an HTTP result, leveraging built-in RFC 7807 support. ```csharp using UnionRailway; using UnionRailway.AspNetCore; var result = await GetUserAsync(id); return result.ToHttpResult(); // RFC 7807 automatic ``` -------------------------------- ### Future Native C# Union Types Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Shows the expected syntax for native union types in C# when .NET 11 stable is released, highlighting zero application code changes. ```csharp public union Rail(T, UnionError); // Native union types public union UnionError(...); // Native discriminated union ``` -------------------------------- ### Handle Error Cases Explicitly Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Always check for and handle error cases instead of assuming success. Use methods like IsSuccess or Match for safe error management. ```csharp // ❌ BAD: Assuming success without checking var user = result.Unwrap(); // May throw UnwrapException! // ✅ GOOD: Check first or use Match if (result.IsSuccess(out var user, out var error)) { // use user } else { // handle error } // OR use Match var message = result.Match( onSuccess: user => $"Hello {user.Name}", onError: error => $"Error: {error}"); ``` -------------------------------- ### Await Async Chains Correctly Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Ensure that asynchronous operations within chains are properly awaited. Returning a Task> instead of awaiting it can lead to compilation errors. ```csharp // ❌ BAD: Returning Task> instead of Rail public async Task GetUser(int id, UserService svc) { var result = svc.GetUserAsync(id); // Missing await! return result.ToHttpResult(); // Won't compile! } // ✅ GOOD public async Task GetUser(int id, UserService svc) { var result = await svc.GetUserAsync(id); return result.ToHttpResult(); } // ✅ EVEN BETTER: Use ToHttpResultAsync public async Task GetUser(int id, UserService svc) { return await svc.GetUserAsync(id).ToHttpResultAsync(); } ``` -------------------------------- ### Chaining Asynchronous Operations with Railway Composition Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Compose asynchronous operations using BindAsync and MapAsync, then convert the final result to an HTTP result. ```csharp var result = await GetUserAsync(id) .BindAsync(user => GetOrdersAsync(user.Id)) .MapAsync(orders => new OrderSummary(orders)) .ToHttpResultAsync(); ``` -------------------------------- ### Wrapping Nullable Returns with UnionWrapper Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Utilize UnionWrapper.RunNullableAsync to convert nullable return types from legacy code into a UnionRailway result. ```csharp // Wrap nullable returns: var maybeUser = await UnionWrapper.RunNullableAsync(() => repo.FindAsync(id)); ``` -------------------------------- ### Entity Framework Core FirstOrDefaultAsUnionAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Execute a FirstOrDefault query on Entity Framework Core and map the result to a Rail type. ```csharp // FirstOrDefault → Rail var user = await db.Users.FirstOrDefaultAsUnionAsync("User", x => x.Id == id); ``` -------------------------------- ### Executing Side Effects with TapAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Employ TapAsync to perform side effects, such as logging, without altering the success value of the railway operation. ```csharp // Execute side effects without changing the value var result = await GetUserAsync(id) .TapAsync(user => LogAccessAsync(user.Id)) .ToHttpResultAsync(); ``` -------------------------------- ### Handling Union-Ready Results in .NET Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html This snippet shows how to check for errors and extract data from a union-ready result object. It's useful when dealing with operations that can either succeed with data or fail with a specific error type. ```.cs if (eo.IsError) return eo.Errors[0]; // After: Rail rail = await repo.GetAsync(id); if (!rail.IsSuccess(out var user, out var err)) return err.GetValueOrDefault(); ``` -------------------------------- ### Avoid Mixing Error Handling Styles Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Refrain from mixing exceptions with the Railway error handling style. Use the pure Railway style for consistent error management. ```csharp // ❌ BAD: Mixing exceptions with Railway public async ValueTask> GetUserAsync(int id) { try { var user = await db.Users.FindAsync(id); return user ?? throw new NotFoundException(); // ❌ Don't throw! } catch (Exception ex) { return new UnionError.SystemFailure(ex); } } // ✅ GOOD: Pure Railway style public async ValueTask> GetUserAsync(int id) { var user = await db.Users.FindAsync(id); return user ?? new UnionError.NotFound("User"); } ``` -------------------------------- ### Recover Operation for Fallback Values Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Demonstrates the Recover operation, which provides a fallback value or action when a specific error type is encountered in a Rail. ```csharp // ── Recover: provide a fallback for specific error types ───────────────── Rail withFallback = await GetUserAsync(id) .RecoverAsync(_ => guestUser); // More selective recovery Rail partial = await GetUserAsync(id) .RecoverAsync(_ => new User { Id = 0, Name = "Anonymous" }); ``` -------------------------------- ### Minimal API Endpoint with UnionRailway Result Conversion Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html This Minimal API endpoint utilizes the `Rail` returned from the service and converts it to an `IResult` using `.ToHttpResult()`. This provides a clean, one-line endpoint definition. ```csharp // Minimal API endpoint — one line app.MapGet("/users/{id:int}", async (int id, UserService svc) => (await svc.GetUserAsync(id)).ToHttpResult()); ``` -------------------------------- ### Converting UnionRailway Result to HTTP Response Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Shows how to convert a `Rail` result from a service into an HTTP response using `.ToHttpResult()`, which automatically handles RFC 7807 Problem Details. ```csharp app.MapGet("/users/{id}", async (int id, UserService service) => { var result = await service.GetUserAsync(id); return result.ToHttpResult(); // ✅ Automatic RFC 7807 Problem Details }); ``` -------------------------------- ### Entity Framework Core SaveChangesAsUnionAsync Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Execute SaveChanges on Entity Framework Core and map the result to a Rail type. ```csharp // SaveChanges → Rail var result = await db.SaveChangesAsUnionAsync(); ``` -------------------------------- ### Avoid Generic Bind with Anonymous Types Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Do not use the generic Bind method with anonymous types, as it can lead to type inference issues. Use BindObject instead. ```csharp // ❌ BAD: Generic type inference issues! .Bind(p => Union.Ok(new { p.Id, p.Name })) // May serialize as Rail wrapper instead of the object! // ✅ GOOD: Use BindObject instead .BindObject(p => new { p.Id, p.Name }) ``` -------------------------------- ### UnionRailway Performance Benchmarks Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/COMPARISON.md Provides benchmark results for UnionRailway operations, highlighting its performance characteristics. ```text BenchmarkDotNet=v0.14.0, OS=Windows, .NET 8.0 UnionRailway Performance: | Operation | Mean | Allocated | Notes | |-----------|------|-----------|-------| | **Creation** | | Create success Rail | 0.5 ns | 0 B | ✅ Stack-only, zero allocation | | Create failure Rail | 2.8 ns | 24 B | Error record allocation | | Implicit conversion | <1 ns | 0 B | Zero overhead | | **Pattern Matching** | | IsSuccess pattern | 0.6 ns | 0 B | Inline check | | Match pattern | 0.8 ns | 0 B | Zero-cost abstraction | | TryGetValue pattern | <1 ns | 0 B | Direct struct access | | **Railway Operations** | | Map (success) | 1.3 ns | 0 B | AggressiveInlining | | Map (error) | 3.6 ns | 24 B | Short-circuit | | Bind (success) | 36 ns | 40 B | Function call overhead | | Chain 3x Map | 7.2 ns | 0 B | Zero-allocation chain | | **Async Operations** | | MapAsync | 48 ns | 0 B | ValueTask overhead only | | BindAsync | 100 ns | 40 B | Minimal async allocation | | **Error Handling** | | NotFound error | 1.8 ns | 24 B | Simple error record | | Validation error | 103 ns | 464 B | Dictionary creation | | **Real-World Scenarios** | | Service call chain (3 ops) | 20 ns | 120 B | Practical overhead | | Error handling scenario | 4.6 ns | 24 B | Fast failure path | ``` -------------------------------- ### Creating Custom Domain Errors Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Define application-specific errors with custom status codes and metadata using UnionError.Custom. This allows for detailed error reporting, compatible with RFC 7807 ProblemDetails. ```csharp // Application-specific error with any HTTP status + metadata return new UnionError.Custom( Code: "RATE_LIMIT_EXCEEDED", Message: "Too many requests, please retry later.", StatusCode: 429, Extensions: new Dictionary { ["retryAfter"] = 30, ["limit"] = 100 }); // Produces RFC 7807 ProblemDetails: // { // "status": 429, // "title": "RATE_LIMIT_EXCEEDED", // "detail": "Too many requests...", // "retryAfter": 30, // "limit": 100 // } ``` -------------------------------- ### Tap Operation for Side Effects Source: https://github.com/salihcantekin/unionrailway/blob/main/docs/index.html Shows the Tap operation, which allows performing side effects (like logging) only on the success path of a Rail without altering the value. ```csharp // ── Tap: side effects without altering the value ───────────────────────── var result = await GetUserAsync(id) .TapAsync(u => logger.LogInformation("Accessed user {Id}", u.Id)) .ToHttpResultAsync(); // side effect only on success path ``` -------------------------------- ### Using BindObject for Anonymous Types and Object Results Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Employ BindObject when dealing with object or anonymous type results to prevent generic type inference issues and handle conditional logic. ```csharp // ✅ GOOD: Use BindObject for object/anonymous type results var result = await GetProductAsync(id) .BindObject( product => product.Stock > 0, product => new { product.Id, product.Name, Status = "In Stock" }, product => new UnionError.Conflict("Out of stock")); // ✅ GOOD: Classic style also works var result = await GetProductAsync(id) .BindObject(product => product.Stock > 0 ? Union.Ok(new { product.Id, product.Name }) // Union.Ok(object) overload : Union.Fail(new UnionError.Conflict("Out of stock"))); // ❌ AVOID: Generic inference issues with Bind var result = await GetProductAsync(id) .Bind(product => Union.Ok(new { product.Id })); // May cause type issues! ``` -------------------------------- ### Avoid Over-Using object as Return Type Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Minimize the use of 'object' as a return type to maintain type safety. Box to 'object' only when necessary, such as for DTOs at the HTTP boundary. ```csharp // ❌ BAD: Loses type safety public ValueTask> GetUserAsync(int id) { return db.Users.FirstOrDefaultAsUnionAsync("User", x => x.Id == id) .MapAsync(user => (object)user); // Why box? } // ✅ GOOD: Keep strong typing as long as possible public ValueTask> GetUserAsync(int id) { return db.Users.FirstOrDefaultAsUnionAsync("User", x => x.Id == id); } // Use object only at HTTP boundary for DTOs: .MapAsync(user => new { user.Id, user.Name }) .ToHttpResultAsync(); ``` -------------------------------- ### Use BindObject for Anonymous Types Source: https://github.com/salihcantekin/unionrailway/blob/main/README.md Use BindObject when you need to project results into anonymous types or when the result type is object. This ensures type safety and clarity. ```csharp // ✅ GOOD: Type-safe and clear .BindObject( product => product.Stock > 0, product => new { product.Id, product.Name, Status = "Available" }, product => new UnionError.Conflict("Out of stock")) ```