{"id":37530809,"url":"https://github.com/mrxrsd/arpl","last_synced_at":"2026-01-16T08:32:00.889Z","repository":{"id":288812133,"uuid":"961138733","full_name":"mrxrsd/arpl","owner":"mrxrsd","description":"Lightweight C# library for functional error handling using Either and Result types. An advanced Result Pattern type-safe, expressive, and designed for clean code.","archived":false,"fork":false,"pushed_at":"2025-09-28T02:33:09.000Z","size":264,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-19T21:12:23.541Z","etag":null,"topics":["csharp","dotnet","either","error-handling","funcional-programming","monad","result-pattern","result-type","validation"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrxrsd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-04-05T20:50:25.000Z","updated_at":"2025-09-28T02:32:53.000Z","dependencies_parsed_at":"2025-09-13T21:36:15.070Z","dependency_job_id":null,"html_url":"https://github.com/mrxrsd/arpl","commit_stats":null,"previous_names":["mrxrsd/arpl"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/mrxrsd/arpl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrxrsd%2Farpl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrxrsd%2Farpl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrxrsd%2Farpl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrxrsd%2Farpl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrxrsd","download_url":"https://codeload.github.com/mrxrsd/arpl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrxrsd%2Farpl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","either","error-handling","funcional-programming","monad","result-pattern","result-type","validation"],"created_at":"2026-01-16T08:32:00.816Z","updated_at":"2026-01-16T08:32:00.871Z","avatar_url":"https://github.com/mrxrsd.png","language":"C#","readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\".github/img/logo.png\" alt=\"drawing\" width=\"700px\"/\u003e\u003c/br\u003e\n\n\u003c/div\u003e\n\n\n[![NuGet](https://img.shields.io/nuget/v/ARPL.svg)](https://www.nuget.org/packages/ARPL)\n[![Downloads](https://img.shields.io/nuget/dt/ARPL.svg)](https://www.nuget.org/packages/ARPL)\n[![Build](https://github.com/mrxrsd/arpl/actions/workflows/build.yml/badge.svg)](https://github.com/mrxrsd/arpl/actions/workflows/build.yml)\n[![codecov](https://codecov.io/gh/mrxrsd/arpl/graph/badge.svg?token=L3TRITTYQ2)](https://codecov.io/gh/mrxrsd/arpl)\n[![License](https://img.shields.io/github/license/mrxrsd/arpl.svg)](https://github.com/mrxrsd/arpl/blob/main/LICENSE)\n\n\n\u003e If you find ARPL helpful, please give it a star ⭐! It helps the project grow and improve.\n\n\n# ARPL\n\nA lightweight C# library providing robust discriminated unions for error handling and functional programming patterns. ARPL offers two main types: `Either\u003cL,R\u003e` for generic discriminated unions and `SResult\u003cR\u003e` for specialized success/error handling.\n\n## Why ARPL? 🤔\n\n- ✨ **Type-safe error handling** without exceptions\n- 🔄 **Rich functional methods** for composing operations\n- 🎯 **Explicit error cases** in method signatures\n- 📦 **Collection of errors** support out of the box\n- 🔗 **Chainable operations** with fluent API\n- 🧪 **Testable code** with predictable flows\n\n## Table of Contents 📑\n\n- [The Result Pattern](#the-result-pattern-)\n- [Features](#features-)\n- [Getting Started](#getting-started-)\n  - [Installation](#installation)\n  - [Basic Usage](#basic-usage)\n    - [Either\u003cL,R\u003e](#eitherLR)\n    - [SResult\u003cR\u003e](#sresultr)\n    - [Error](#error)\n- [Error Handling](#error-handling)\n  - [Single Errors](#single-errors)\n  - [Multiple Errors](#multiple-errors)\n- [Bespoke Errors](#bespoke-errors)\n- [Implicit Conversions](#implicit-conversions)\n- [StaticFactory Helpers](#staticfactory-helpers)\n- [Type Features](#type-features)\n  - [Either\u003cL,R\u003e](#eitherLR-1)\n  - [SResult\u003cR\u003e](#sresultr-1)\n  - [Error](#error-1)\n- [Functional Methods](#functional-methods-)\n  - [Map \u0026 MapAsync](#map--mapasync)\n  - [Bind \u0026 BindAsync](#bind--bindasync)\n  - [Match \u0026 MatchAsync](#match--matchasync)\n  - [Sequence \u0026 SequenceAsync](#sequence--sequenceasync)\n  - [Traverse \u0026 TraverseAsync](#traverse--traverseasync)\n  - [Try \u0026 TryAsync](#try--tryasync)\n  - [Apply \u0026 ApplyAsync](#apply--applyasync)\n  - [Do \u0026 DoAsync](#do--doasync)\n  - [Transform \u0026 TransformAsync](#transform--transformasync)\n  - [Mixing Sync and Async Methods](#mixing-sync-and-async-methods)\n- [Best Practices](#best-practices)\n  - [Anti-Patterns to Avoid](#anti-patterns-to-avoid)\n- [Demo Application](#demo-application)\n  - [Features](#features-1)\n  - [Running the Demo](#running-the-demo)\n  - [API Endpoints](#api-endpoints)\n  - [Example Request](#example-request)\n- [Benchmarking](#benchmarking)\n- [ASP.NET Core Integration](#aspnet-core-integration-)\n  - [Installation](#installation-1)\n  - [Basic Usage](#basic-usage-1)\n  - [Custom Configuration](#custom-configuration)\n- [Contributing](#contributing-)\n- [License](#license-)\n\n## The Result Pattern 🎯\n\nThe Result Pattern is an elegant alternative to traditional exception handling that makes error cases explicit in your code. Instead of throwing exceptions that can be caught anywhere in the call stack, methods return a Result type that can represent either success or failure.\n\n### Traditional Exception Handling\n\n```csharp\npublic User CreateUser(string email, string password)\n{\n    try\n    {\n        // Validate email\n        if (string.IsNullOrEmpty(email))\n            throw new ValidationException(\"Email is required\");\n            \n        if (!IsValidEmail(email))\n            throw new ValidationException(\"Invalid email format\");\n\n        // Validate password\n        if (string.IsNullOrEmpty(password))\n            throw new ValidationException(\"Password is required\");\n\n        // Create and save user\n        var user = new User(email, password);\n        await _repository.Save(user);\n\n        return user;\n    }\n    catch (ValidationException ex)\n    {\n        _logger.LogWarning(ex, \"Validation failed when creating user\");\n        throw; \n    }\n    catch (Exception ex)\n    {\n        _logger.LogError(ex, \"Unexpected error creating user\");\n        throw;\n    }\n}\n\n```\n\n### Using ARPL's Result Pattern\n\n```csharp\npublic SResult\u003cUser\u003e CreateUser(string email, string password)\n{\n    // Validate inputs\n    if (string.IsNullOrEmpty(email))\n        return Fail\u003cUser\u003e(Errors.New(\"Email is required\"));\n\n    if (string.IsNullOrEmpty(password))\n        return Fail\u003cUser\u003e(Errors.New(\"Password is required\"));\n        \n    if (!IsValidEmail(email))\n        return Fail\u003cUser\u003e(Errors.New(\"Invalid email format\"));\n\n    // Create and save user\n    try\n    {\n        var user = new User(email, password);\n        _repository.Save(user);\n \n        return Success(user);\n    }\n    catch (Exception ex)\n    {\n        return Fail\u003cUser\u003e(Errors.New(ex, \"Failed to create user\")); //Unexpected Error\n    }\n}\n```\n\n### Benefits of the Result Pattern\n\n1. **Explicit Error Handling**: Error cases are part of the method signature, making it clear that a method can fail and forcing error handling at compile time.\n\n2. **Type Safety**: The compiler ensures you handle both success and error cases through pattern matching, preventing runtime surprises.\n\n3. **No Exceptions**: Better performance by avoiding the overhead of exception handling and stack traces. Exceptions are reserved for truly exceptional cases.\n\n4. **Composable**: Easy to chain operations using functional methods like `Map` and `Bind`, making the code more readable and maintainable.\n\n5. **Rich Error Types**: Built-in support for different error types and error collections, allowing for more granular error handling.\n\n6. **Predictable Flow**: All possible outcomes are clearly defined and handled in a structured way, making the code easier to reason about.\n\n7. **Better Testing**: Easier to test error cases since they're explicit in the type system rather than relying on exception handling.\n\n## Features 🚀\n\n- **Either\u003cL,R\u003e** - A generic discriminated union that can hold one of two possible types\n- **SResult\u003cR\u003e** - A specialized result type for handling success/error scenarios\n- **Implicit conversions** between `Either\u003cError,R\u003e` and `SResult\u003cR\u003e`\n- **Pattern matching** support for elegant value handling\n- **Type-safe error handling** without exceptions\n- **Functional programming** friendly design\n\n## Getting Started 🏃\n\n### Installation\n\nInstall via NuGet:\n\n```shell\nInstall-Package ARPL\n```\n\n### Basic Usage\n\n#### Either\u003cL,R\u003e\n\nA generic discriminated union that can hold one of two possible types:\n\n```csharp\n// Create Either instances\nvar right = Either\u003cstring, int\u003e.Right(42);        // Success path\nvar left = Either\u003cstring, int\u003e.Left(\"error\");     // Error path\n\n// Check which value is present\nif (right.IsRight)\n    Console.WriteLine($\"Value: {right.RightValue}\"); // 42\n\nif (left.IsLeft)\n    Console.WriteLine($\"Value: {left.LeftValue}\"); // error\n\n// Pattern match to handle both cases\nvar message = left.Match(\n    left =\u003e $\"Error: {left}\",\n    right =\u003e $\"Value: {right}\");\n```\n\n#### SResult\u003cR\u003e\n\nA specialized result type for success/error scenarios:\n\n```csharp\n// Create results\nvar success = SResult\u003cint\u003e.Success(42);                     // Success case\nvar error = SResult\u003cint\u003e.Fail(Errors.New(\"Invalid input\")); // Error case\n\n// Check result type\nif (success.IsSuccess)\n    Console.WriteLine($\"Value: {success.SuccessValue}\");\nelse\n    Console.WriteLine($\"Error: {success.ErrorValue.Message}\");\n\n// Pattern match for handling\nvar message = error.Match(\n    error =\u003e $\"Failed: {error.Message}\",\n    value =\u003e $\"Success: {value}\");\n```\n\n#### Error\n\nA rich error type with support for messages, codes, and chaining:\n\n```csharp\n// Create errors\nvar simple = Errors.New(\"Something went wrong\");\nvar coded = Errors.New(\"Invalid input\", \"INVALID_INPUT\");\nvar chained = simple + coded;\n\n// Access error details\nConsole.WriteLine(simple.Message);     // \"Something went wrong\"\nConsole.WriteLine(coded.Code);        // \"INVALID_INPUT\"\nConsole.WriteLine(chained.Message)   // [\"Something went wrong\",\"Invalid input\"] \n```\n## Error Handling\n\nARPL provides a flexible error handling system that allows you to work with both single errors and collections of errors. The `Error` class serves as the base for all error types, and the `ErrorCollection` allows you to aggregate multiple errors together.\n\n### Single Errors\n\n```csharp\n// Create a simple error\nvar error = Errors.New(\"Invalid input\", \"ERR001\");\n\n// Create an unexpected error from an exception\nvar unexpectedError = Errors.New(new Exception(\"Database connection failed\"));\n\n// Check error types\nif (error.HasErrorOf\u003cExpectedError\u003e())\n    Console.WriteLine(\"This is an expected error\");\n\n// Check exception types\nif (unexpectedError.HasExceptionOf\u003cDbException\u003e())\n    Console.WriteLine(\"This is a database error\");\n```\n\n### Multiple Errors\n\nWhen you need to collect and combine multiple errors, use `ErrorCollection`:\n\n```csharp\n// Start with an empty error collection\nvar errors = Errors.EmptyError();\n\n// Add errors as they are found\nerrors.Add(Errors.New(\"Invalid email\", \"VAL001\"));\nerrors.Add(Errors.New(\"Password too short\", \"VAL002\"));\n\n// You can also combine errors using the + operator\nvar error1 = Errors.New(\"Field required\", \"VAL003\");\nvar error2 = Errors.New(\"Invalid format\", \"VAL004\");\nvar combined = error1 + error2; // Implicit creates a new ErrorCollection\ncombined += Errors.New(\"Missing argument\", \"VAL005\");\n\n// Enumerate through errors\nforeach (var error in combined.AsEnumerable())\n{\n    Console.WriteLine($\"{error.Code}: {error.Message}\");\n}\n\n// Get error count\nConsole.WriteLine($\"Total errors: {combined.Count}\"); // 3\n\n// Check if collection has specific error\nvar hasValidationError = combined.AsEnumerable().Any(e =\u003e e.Code.StartsWith(\"VAL\"));\n\n// Use in result types\nreturn SResult\u003cUser\u003e.Error(errors); // Works with both single Error and ErrorCollection\n```\n\n## Bespoke Errors\n\nARPL allows you to create custom error types by extending the `Error` class. This enables you to create domain-specific errors that carry meaningful context for your application:\n\n```csharp\npublic record NotFoundError : Error\n{\n    public NotFoundError(string entityType, string identifier)\n    {\n        EntityType = entityType;\n        Identifier = identifier;\n    }\n\n    public string EntityType { get; }\n    public string Identifier { get; }\n    public override string Message =\u003e $\"{EntityType} with id {Identifier} was not found\";\n    public override bool IsExpected =\u003e true;\n}\n\n// Usage example:\npublic async Task\u003cSResult\u003cUser\u003e\u003e GetUserById(string userId)\n{\n    var user = await _repository.FindUserById(userId);\n    if (user == null)\n        return SResult\u003cUser\u003e.Error(new NotFoundError(\"User\", userId));\n\n    return SResult\u003cUser\u003e.Success(user);\n}\n\n// Pattern matching with custom error\nvar result = await GetUserById(\"123\");\nvar message = result.Match(\n    fail =\u003e fail is NotFoundError nf \n        ? $\"Could not find {nf.EntityType} {nf.Identifier}\" \n        : \"Unknown error\",\n    success =\u003e $\"Found user: {success.Name}\"\n);\n```\n\nBespoke errors provide several benefits:\n1. Type-safe error handling with pattern matching\n2. Rich error context specific to your domain\n3. Clear distinction between expected and unexpected errors\n4. Consistent error handling across your application\n\n## Implicit Conversions\n\nARPL supports several implicit conversions to make your code more concise and readable.\n\n### Either from Base Types\n\nYou can create an `Either\u003cL, R\u003e` instance directly from values of type `L` or `R`:\n\n```csharp\n// Implicitly create a Right-sided Either\nEither\u003cstring, int\u003e success = 42;\n\n// Implicitly create a Left-sided Either\nEither\u003cstring, int\u003e error = \"Something went wrong\";\n\n// This is equivalent to:\nvar successExplicit = Either\u003cstring, int\u003e.Right(42);\nvar errorExplicit = Either\u003cstring, int\u003e.Left(\"Something went wrong\");\n```\n\n### Between `Either\u003cError,R\u003e` and `SResult\u003cR\u003e`\n\nARPL also supports seamless conversion between `Either\u003cError,R\u003e` and `SResult\u003cR\u003e`:\n\n```csharp\n// Convert from Either to SResult\nEither\u003cError, int\u003e either = Either\u003cError, int\u003e.Right(42);\nSResult\u003cint\u003e result = either; // Implicit conversion\n\n// Convert from SResult to Either\nSResult\u003cint\u003e sresult = SResult\u003cint\u003e.Success(42);\nEither\u003cError, int\u003e converted = sresult; // Implicit conversion\n```\n\n\u003e **Note:** The implicit conversion between `Either` and `SResult` only works for `Either\u003cError, R\u003e`. Attempting to convert other `Either` types will result in a compile-time error or throw an exception.\n\n## StaticFactory Helpers\n\nFor a more functional and concise style, ARPL provides the `StaticFactory` class, which offers utility methods to create instances of `SResult` and `Either` in a direct and expressive way:\n\n```csharp\nusing static Arpl.Core.StaticFactory;\n\n// Create a success result\nvar success = Success(42); // SResult\u003cint\u003e\n\n// Create a failure result\nvar fail = Fail\u003cint\u003e(new Error(\"fail\")); // SResult\u003cint\u003e\n\n// Create an Either with a left value\nvar left = Left\u003cstring, int\u003e(\"error\"); // Either\u003cstring, int\u003e\n\n// Create an Either with a right value\nvar right = Right\u003cstring, int\u003e(42); // Either\u003cstring, int\u003e\n\n// Try example\nvar result = Try(() =\u003e Success(int.Parse(input))); // SResult\u003cint\u003e\n```\n\nAvailable factory methods:\n\n1. **Success\u003cT\u003e(T value)**: Creates a successful `SResult\u003cT\u003e`\n2. **Fail\u003cT\u003e(Error value)**: Creates a failed `SResult\u003cT\u003e`\n3. **Left\u003cL,R\u003e(L value)**: Creates an `Either\u003cL,R\u003e` with left value\n4. **Right\u003cL,R\u003e(R value)**: Creates an `Either\u003cL,R\u003e` with right value\n5. **Try\u003cR\u003e(Func\u003cSResult\u003cR\u003e\u003e)**: Safely executes a function that returns `SResult\u003cR\u003e`\n6. **TryAsync\u003cR\u003e(Func\u003cTask\u003cSResult\u003cR\u003e\u003e\u003e)**: Safely executes an async function that returns `Task\u003cSResult\u003cR\u003e\u003e`\n\n## Type Features\n\n### Either\u003cL,R\u003e\n\n- `Left(L value)` - Creates a new Either instance containing a left value\n- `Right(R value)` - Creates a new Either instance containing a right value\n- `IsLeft` - Indicates if the instance contains a left value\n- `IsRight` - Indicates if the instance contains a right value\n- `LeftValue` - Gets the left value (if present)\n- `RightValue` - Gets the right value (if present)\n- `Match` - Pattern matching for transforming or handling the contained value\n- `MatchAsync` - Asynchronous pattern matching for handling the contained value\n- `Map` - Transforms the right value using a mapping function (if present)\n- `MapAsync` - Transforms the right value using an async mapping function (if present)\n- `Bind` - Chains operations that return Either (monadic bind)\n- `BindAsync` - Asynchronously chains operations that return Either\n- `Apply` - Transforms both left and right values into a new Either\n- `ApplyAsync` - Asynchronously transforms both left and right values into a new Either\n- `Sequence` - Transforms a collection of Either into an Either of collection\n- `SequenceAsync` - Asynchronously transforms a collection of Either into an Either of collection\n- `Traverse` - Maps and sequences in one step\n- `TraverseAsync` - Asynchronously maps and sequences in one step\n\n### SResult\u003cR\u003e\n\n- `Success(R value)` - Creates a new success result\n- `Error(Error value)` - Creates a new error result\n- `IsSuccess` - Indicates if the result represents success\n- `IsFail` - Indicates if the result represents an error\n- `SuccessValue` - Gets the success value\n- `ErrorValue` - Gets the error value\n- `Match` - Pattern matching for transforming or handling the result\n- `MatchAsync` - Asynchronous pattern matching for handling the result\n- `AsEither` - Converts the SResult\u003cR\u003e to Either\u003cError, R\u003e\n- `Map` - Transforms the success value using a mapping function (if present)\n- `MapAsync` - Transforms the success value using an async mapping function (if present)\n- `Bind` - Chains operations that return SResult (monadic bind)\n- `BindAsync` - Asynchronously chains operations that return SResult\n- `Apply` - Transforms both error and success values into a new SResult\n- `ApplyAsync` - Asynchronously transforms both error and success values into a new SResult\n- `Try` - Executes a function safely, catching any exceptions into an error result\n- `TryAsync` - Executes an async function safely, catching any exceptions into an error result\n- `Sequence` - Transforms a collection of SResult into an SResult of collection\n- `SequenceAsync` - Asynchronously transforms a collection of SResult into an SResult of collection\n- `Traverse` - Maps and sequences in one step\n- `TraverseAsync` - Asynchronously maps and sequences in one step\n\n### Error\n\n- `New(string message)` - Creates a new expected error with a message\n- `New(string message, string code)` - Creates a new expected error with a message and code\n- `New(Exception ex)` - Creates a new unexpected error from an exception\n- `New(Exception ex, string message, string code)` - Creates a new unexpected error with a message and a code\n- `Message` - Gets the error message\n- `Code` - Gets the error code (if present)\n- `Exception` - Gets the exception (if present)\n- `IsExpected` - Indicates if the error was expected\n- `HasErrorOf\u003cT\u003e()` - Checks if the error is of type T\n- `HasExceptionOf\u003cT\u003e()` - Checks if the error's exception is of type T\n\n## Functional Methods 🧮\n\nARPL provides a rich set of functional methods to compose and transform values:\n\n### Map \u0026 MapAsync\n\nTransform the success/right value while preserving the context:\n\n```csharp\n// Map a successful value\nSResult\u003cint\u003e result = SResult\u003cint\u003e.Success(42);\nvar doubled = result.Map(x =\u003e x * 2); // Success(84)\n\n// Map with Either\nEither\u003cError, int\u003e either = Either\u003cError, int\u003e.Right(42);\nvar doubled = either.Map(x =\u003e x * 2); // Right(84)\n\n// Async mapping\nvar asyncResult = await result.MapAsync(async x =\u003e {\n    await Task.Delay(100); // Simulate async work\n    return x * 2;\n});\n```\n\n### Bind \u0026 BindAsync\n\nChain operations that might fail:\n\n```csharp\n// Simple validation chain\nSResult\u003cint\u003e Parse(string input) =\u003e\n    int.TryParse(input, out var number)\n        ? Success(number)\n        : Fail\u003cint\u003e(Errors.New(\"Invalid number\"));\n\nSResult\u003cint\u003e Validate(int number) =\u003e\n    number \u003e 0\n        ? Success(number)\n        : Fail\u003cint\u003e(Errors.New(\"Number must be positive\"));\n\n// Chain operations with Bind\nvar result = Parse(\"42\")\n    .Bind(Validate)\n    .Map(x =\u003e x * 2); // Success(84)\n```\n\n### Match \u0026 MatchAsync\n\nPattern match to handle both success and error cases:\n\n```csharp\n// Handle validation result\nvar result = ValidateAge(age);\nvar message = result.Match(\n    error =\u003e $\"Invalid age: {error.Message}\",\n    age =\u003e $\"Age {age} is valid\");\n\n// Format API response\nvar apiResult = await GetUserAsync(id);\nvar response = apiResult.Match(\n    error =\u003e new ErrorResponse { Code = error.Code, Message = error.Message },\n    user =\u003e new UserResponse { Id = user.Id, Name = user.Name });\n\n// With Either for custom error handling\nvar parseResult = TryParseJson\u003cUserData\u003e(json);\nvar data = parseResult.Match(\n    error =\u003e new UserData { IsValid = false, Error = error },\n    success =\u003e success with { IsValid = true });\n```\n\n### Sequence \u0026 SequenceAsync\n\nTransform a collection of results into a result of collection:\n\n```csharp\n// Sequence a list of results\nvar results = new[] {\n    SResult\u003cint\u003e.Success(1),\n    SResult\u003cint\u003e.Success(2),\n    SResult\u003cint\u003e.Success(3)\n};\nvar combined = results.Sequence(); // Success([1,2,3])\n\n// If any fails, the whole operation fails\nvar mixed = new[] {\n    SResult\u003cint\u003e.Success(1),\n    SResult\u003cint\u003e.Fail(Errors.New(\"Oops\")),\n    SResult\u003cint\u003e.Success(3)\n};\nvar failed = mixed.Sequence(); // Fail(\"Oops\")\n```\n\n### Traverse \u0026 TraverseAsync\n\nMap and sequence in one step:\n\n```csharp\n// Parse a list of strings into numbers\nvar strings = new[] { \"1\", \"2\", \"3\" };\nvar numbers = strings.Traverse(str =\u003e \n    int.TryParse(str, out var num)\n        ? Success(num)\n        : Fail\u003cint\u003e(Errors.New($\"Invalid number: {str}\")));\n\n// Async traversal\nvar urls = new[] { \"url1\", \"url2\" };\nvar contents = await urls.TraverseAsync(async url =\u003e {\n    try {\n        var content = await httpClient.GetStringAsync(url);\n        return Success(content);\n    }\n    catch (Exception ex) {\n        return Fail\u003cstring\u003e(Errors.New(ex, $\"Failed to fetch {url}\"));\n    }\n});\n```\n\n### Try \u0026 TryAsync\n\nStart functional chains with exception-safe operations:\n\n```csharp\n// Start a chain with Try for sync operations\nvar result = Try(() =\u003e int.Parse(input))        // Returns SResult\u003cint\u003e\n    .Map(x =\u003e x * 2)                           // Transform if successful\n    .Bind(x =\u003e Validate(x));                   // Chain with another operation\n\n// Complex validation chain starting with Try\nvar userResult = Try(() =\u003e {\n    if (string.IsNullOrEmpty(email))\n        return Fail\u003cUser\u003e(Errors.New(\"Email required\"));\n    return Success(new User(email));\n})\n.Bind(ValidateUser)                            // Chain with other validations\n.BindAsync(SaveUserAsync);                     // Continue with async operations\n\n// Start async chains with TryAsync\nvar apiResult = await TryAsync(async () =\u003e {\n    var response = await httpClient.GetAsync(url);\n    return response.IsSuccessStatusCode\n        ? Success(await response.Content.ReadAsStringAsync())\n        : Fail\u003cstring\u003e(Errors.New($\"API error: {response.StatusCode}\"));\n})\n.Map(json =\u003e JsonSerializer.Deserialize\u003cUser\u003e(json))  // Transform the result\n.BindAsync(ValidateUserAsync);                       // Continue the chain\n```\n\n### Apply \u0026 ApplyAsync\n\nTransform both success and error cases:\n\n```csharp\n// Convert errors to user-friendly messages\nvar result = SResult\u003cint\u003e.Fail(Errors.New(\"INVALID_INPUT\"));\nvar friendly = result.Apply(\n    error =\u003e SResult\u003cstring\u003e.Success($\"Please try again: {error.Message}\"),\n    value =\u003e SResult\u003cstring\u003e.Success($\"Your number is {value}\"));\n\n// With Either for custom error handling\nvar either = Either\u003cint, string\u003e.Left(404);\nvar handled = either.Apply(\n    status =\u003e Either\u003cstring, string\u003e.Right($\"Error {status}\"),\n    content =\u003e Either\u003cstring, string\u003e.Right($\"Content: {content}\"));\n```\n\n### Do \u0026 DoAsync\n\nThe `Do` and `DoAsync` methods allow you to perform actions on the monad while ensuring it remains a monad. Unlike `Map`, `Do` requires you to return the same monad type. This is particularly useful when you want to perform actions that should happen regardless of success or error state.\n\n```csharp\n// Using Do with Either - logging regardless of state\nvar result = Either\u003cError, int\u003e.Right(42)\n    .Do(either =\u003e {\n        var state = either.IsRight ? \"success\" : \"error\";\n        _metrics.TrackOperation($\"process_number_{state}\");\n        return either;\n    })\n    .Map(x =\u003e x * 2);\n\n// Using DoAsync with SResult - updating cache regardless of result\nvar asyncResult = await GetUserAsync(id)\n    .DoAsync(async result =\u003e {\n        // Update cache even if the operation failed\n        await _cache.SetAsync($\"user_{id}_last_access\", DateTime.UtcNow);\n        return result;\n    })\n    .Map(user =\u003e user.Name);\n```\n\n### Transform \u0026 TransformAsync\n\nThe `Transform` and `TransformAsync` methods are powerful tools that allow you to access both success and error values of a monad and transform them into any type. Unlike `Map` and `Bind` which only work with the success value, `Transform` gives you access to the entire monad.\n\n```csharp\n// Using Transform with Either\nvar either = Either\u003cstring, int\u003e.Right(42);\nvar message = either.Transform(e =\u003e \n    e.IsRight ? $\"Got number: {e.RightValue}\" : $\"Got error: {e.LeftValue}\");\n\n// Using TransformAsync with SResult\nvar result = await GetOrderAsync(id)\n    .TransformAsync(async order =\u003e {\n        if (order.IsSuccess)\n        {\n            var details = await _detailsService.GetDetailsAsync(order.SuccessValue);\n            return new OrderViewModel(order.SuccessValue, details);\n        }\n        return new OrderViewModel(error: order.ErrorValue.Message);\n    });\n\n// Transform is especially useful for final transformations in a chain\nvar displayText = await ProcessDataAsync()\n    .Map(data =\u003e data.Value)\n    .Bind(ValidateData)\n    .Transform(result =\u003e result.IsSuccess\n        ? $\"Success: {result.SuccessValue}\"\n        : $\"Failed: {result.ErrorValue.Message}\");\n```\n\n\u003e 🕷️ As Uncle Ben said: \"With great power comes great responsibility.\" Transform gives you complete access to the monad's object, breaking away from the usual functional constraints of Map and Bind. Use it wisely, preferably at the end of your chains when you need to make final transformations or present data to external systems.\n\n### Mixing Sync and Async Methods\n\nARPL provides seamless integration between synchronous and asynchronous operations in your functional chains.\n\n\n```csharp\n// Start with async operation\nvar result = await GetUserAsync(id)        // Returns Task\u003cSResult\u003cUser\u003e\u003e\n    .Map(user =\u003e user.Name)               // Sync op, but returns Task\u003cSResult\u003cstring\u003e\u003e\n    .BindAsync(ValidateNameAsync)         // Async op\n    .Map(name =\u003e name.ToUpper());         // Sync op, but returns Task\u003cSResult\u003cstring\u003e\u003e\n\n// Start with sync operation\nvar syncResult = Success(\"test\")           // Returns SResult\u003cstring\u003e\n    .Map(str =\u003e str.ToUpper())           // Still sync, returns SResult\u003cstring\u003e\n    .BindAsync(ValidateAsync)            // Now async! Returns Task\u003cSResult\u003cstring\u003e\u003e\n    .Map(str =\u003e str.Length);             // Sync op, but returns Task\u003cSResult\u003cint\u003e\u003e\n```\n\n\u003e **Note**: Once your chain includes an async operation (like `BindAsync` or `MapAsync`), all subsequent operations become awaitable. This means they will return `Task\u003cT\u003e`, even if the operations themselves are synchronous. ARPL handles this transition automatically, allowing you to write clean code without worrying about async/sync conversions.\n\n## Best Practices\n\n1. Use `Either\u003cL,R\u003e` when you need a generic discriminated union\n2. Use `SResult\u003cR\u003e` for specific success/error handling scenarios\n3. Leverage pattern matching with `Match` for clean and safe value handling\n4. Prefer using the type system for error handling instead of exceptions\n\n\n### Anti-Patterns to Avoid\n\n1. ❌ **Don't mix exceptions with Results**\n```csharp\n// Bad\npublic SResult\u003cUser\u003e GetUser(int id)\n{\n    if (id \u003c= 0)\n        throw new ArgumentException(\"Invalid id\"); // Don't throw!\n\n    var user = _repository.GetById(id);\n    return user == null\n        ? Fail\u003cUser\u003e(Errors.New(\"User not found\"))\n        : Success(user);\n}\n\n// Good\npublic SResult\u003cUser\u003e GetUser(int id)\n{\n    if (id \u003c= 0)\n        return Fail\u003cUser\u003e(Errors.New(\"Invalid id\"));\n\n    var user = _repository.GetById(id);\n    return user == null\n        ? Fail\u003cUser\u003e(Errors.New(\"User not found\"))\n        : Success(user);\n}\n```\n\n2. ❌ **Don't ignore the Result value**\n```csharp\n// Bad\nawait CreateUser(request); // Result ignored!\n\n// Good\nvar result = await CreateUser(request);\nif (result.IsFail)\n    _logger.LogError(\"Failed to create user: {Errors}\", result.ErrorValue);\n```\n\n3. ❌ **Don't use Result for expected flow control**\n```csharp\n// Bad - using Result for normal flow\npublic SResult\u003cdecimal\u003e GetDiscount(User user)\n{\n    return user.IsPremium\n        ? Success(0.1m)\n        : Success(0m);\n}\n\n// Good - use normal return\npublic decimal GetDiscount(User user)\n{\n    return user.IsPremium ? 0.1m : 0m;\n}\n```\n\n## Demo Application\n\nThe repository includes a sample Web API project that demonstrates how to use ARPL in a real-world scenario. The demo implements a simple Person management API with proper error handling and functional programming patterns.\n\n### Features\n- CRUD operations for Person entity\n- Validation using Either\u003cValidateError, T\u003e\n- Error handling with SResult\u003cT\u003e\n- HTTP response handling with custom HttpResult\n\n### Running the Demo\n1. Navigate to the sample directory:\n```bash\ncd sample/SampleWebApi\n```\n\n2. Run the application:\n```bash\ndotnet run\n```\n\n3. Open your browser at:\n- API: http://localhost:5297\n- Swagger UI: http://localhost:5297/swagger\n\n### API Endpoints\n- GET /api/person - List all persons\n- GET /api/person/{id} - Get person by id\n- POST /api/person - Create new person\n\n### Example Request\n```bash\ncurl -X POST http://localhost:5297/api/person \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"John Doe\",\"age\":30}'\n```\n\n## Benchmarking\n\n|Feature|ARPL|FluentResults|OneOf|ErrorOr|\n|-------|----|----|-----|-----|\n|Generic Discriminated Union|✅ `Either\u003cL,R\u003e`|❌|✅|❌|\n|Result Type|✅ `SResult\u003cR\u003e`|✅|❌|✅|\n|Multiple Errors|✅|✅|❌|✅|\n|Functional Methods|✅|✅|❌|✅|\n|Async Support|✅|✅|❌|✅|\n|Pattern Matching|✅|✅|✅|✅|\n|Implicit Conversions|✅|✅|❌|✅|\n|No Dependencies|✅|✅|✅|✅|\n\nARPL combines the best of worlds:\n- Generic discriminated unions like OneOf\n- Rich error handling like FluentResults/ErrorOr\n- Full functional programming support\n- Seamless async/await integration\n\n## ASP.NET Core Integration 🌐\n\n\u003e **Note**: The ASP.NET Core integration package is currently in preview. API may change before the stable release.\n\nARPL provides seamless integration with ASP.NET Core through the `ARPL.AspNetCore` package. This package allows you to easily convert `SResult\u003cT\u003e` to `ActionResult` with custom configuration options.\n\n### Installation\n\n```bash\ndotnet add package ARPL.AspNetCore --version 0.1.0\n```\n\n### Basic Usage\n\nThe package provides extension methods to convert `SResult\u003cT\u003e` to `ActionResult`:\n\n```csharp\npublic class UserController : ControllerBase\n{\n    [HttpGet(\"{id}\")]\n    public async Task\u003cActionResult\u003cUser\u003e\u003e GetUser(int id)\n    {\n        var result = await _userService.GetUserById(id);\n        return result.ToActionResult();\n    }\n}\n```\n\n### Custom Configuration (Optional)\n\nBy default, `ToActionResult()` will:\n- Return `200 OK` with the success value for successful results\n- Return `400 Bad Request` for expected errors (e.g., validation errors)\n- Return `500 Internal Server Error` for unexpected errors (e.g., exceptions)\n\nYou can customize this behavior when you need specific HTTP status codes for your custom error types. This is particularly useful when dealing with domain-specific errors:\n\n```csharp\n\npublic void ConfigureServices(IServiceCollection services)\n{\n    // Configure ARPL (optional)\n    ArplAspNetCore.Setup(cfg =\u003e {\n        // Custom error handler for specific status codes\n        cfg.ErrorHandler = error =\u003e error switch\n        {\n            // Expected errors get 4xx status codes\n            ValidationError =\u003e new BadRequestObjectResult(new { \n                error = \"Validation failed\",\n                details = error.Messages \n            }),\n            NotFoundError =\u003e new NotFoundObjectResult(new { \n                error = \"Resource not found\",\n                details = error.Messages \n            }),\n            // Default fallback\n            _ =\u003e new ObjectResult(new { \n                error = \"Internal server error\",\n                details = error.Messages \n            })\n            {\n                StatusCode = StatusCodes.Status500InternalServerError\n            }\n        };\n    });\n}\n```\n\nThis configuration allows you to:\n- Map specific error types to appropriate HTTP status codes\n- Maintain consistent error response format across your API\n- Handle special cases while keeping the default behavior for common scenarios\n\n## Contributing 🤝\n\nContributions are welcome! Feel free to submit issues and pull requests.\n\n## License 📄\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n---\n\n\u003e **Disclaimer:** This README was generated by Windsurf AI.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrxrsd%2Farpl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrxrsd%2Farpl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrxrsd%2Farpl/lists"}