{"id":15392988,"url":"https://github.com/pimbrouwers/danom","last_synced_at":"2025-06-25T10:34:43.281Z","repository":{"id":254366242,"uuid":"840993415","full_name":"pimbrouwers/Danom","owner":"pimbrouwers","description":"Structures for durable programming patterns in C#.","archived":false,"fork":false,"pushed_at":"2024-12-11T20:26:35.000Z","size":181,"stargazers_count":22,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-07T13:38:50.438Z","etag":null,"topics":["error-handling","monads","option","option-type","railway-oriented-programming","result","result-type"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pimbrouwers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-08-11T10:22:25.000Z","updated_at":"2024-12-13T18:06:52.000Z","dependencies_parsed_at":"2024-11-26T12:38:35.697Z","dependency_job_id":null,"html_url":"https://github.com/pimbrouwers/Danom","commit_stats":{"total_commits":81,"total_committers":1,"mean_commits":81.0,"dds":0.0,"last_synced_commit":"325178bee0c0dc791623dbc5ca5c5a78293a7fb5"},"previous_names":["pimbrouwers/danom"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDanom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDanom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDanom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDanom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pimbrouwers","download_url":"https://codeload.github.com/pimbrouwers/Danom/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240351641,"owners_count":19787880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["error-handling","monads","option","option-type","railway-oriented-programming","result","result-type"],"created_at":"2024-10-01T15:17:00.777Z","updated_at":"2025-06-25T10:34:43.266Z","avatar_url":"https://github.com/pimbrouwers.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Danom\n[![NuGet Version](https://img.shields.io/nuget/v/Danom.svg)](https://www.nuget.org/packages/Danom)\n[![build](https://github.com/pimbrouwers/Danom/actions/workflows/build.yml/badge.svg)](https://github.com/pimbrouwers/Danom/actions/workflows/build.yml)\n[![license](https://img.shields.io/github/license/pimbrouwers/Danom.svg)](https://github.com/pimbrouwers/Danom/blob/master/LICENSE)\n![aot](https://img.shields.io/badge/aot-compatible-green.svg)\n![net9.0](https://img.shields.io/badge/net-9.0-blue.svg)\n![net8.0](https://img.shields.io/badge/net-8.0-blue.svg)\n![netstandard2.1](https://img.shields.io/badge/netstandard-2.1-blue.svg)\n\nDanom is a C# library that provides (monadic) structures to facilitate durable programming patterns in C#, using [Option](#option) and [Result](#result). These discriminated unions are a powerful way to handle nullable values and expected errors in a type-safe manner, while also providing a fluent API for chaining operations.\n\n## Key Features\n\n- Implementation of common monads: [Option](#option) and [Result](#result).\n- Exhaustive matching to prevent null reference exceptions.\n- Fluent API for chaining operations, including async support.\n- Integrated with [ASP.NET Core](#aspnet-core-mvc-integration) and [Fluent Validation](#fluent-validation-integration).\n- API for [parsing strings](#string-parsing) into .NET primitives and value types.\n\n## Design Goals\n\n- Provide a safe and expressive way to handle nullable values.\n- Prevent direct use of internal value, enforcing exhaustive matching.\n- Efficient implementation to minimize overhead.\n- Opionated monads to encourage consistent use.\n\n## Getting Started\n\nInstall the [Danom](https://www.nuget.org/packages/Danom/) NuGet package:\n\n```\nPM\u003e  Install-Package Danom\n```\n\nOr using the dotnet CLI\n```cmd\ndotnet add package Danom\n```\n\n### Quick Start\n\n```csharp\nusing Danom;\n\n//\n// Working with Option type\nvar option = Option.Some(5);\n\noption.Match(\n    some: x =\u003e Console.WriteLine(\"Value: {0}\", x),\n    none: () =\u003e Console.WriteLine(\"No value\"));\n\n// Mapping the value\nvar mappedOption = option.Map(x =\u003e x + 1);\n\n// Binding the option (i.e., when a nested operation also returns an Option)\nvar boundOption = option.Bind(num1 =\u003e\n    num1 % 2 == 0\n        ? Option.Some(num1 / 2)\n        : Option\u003cint\u003e.NoneValue);\n\n// Defaulting the option\nvar defaultOption = option.DefaultValue(99);\nvar defaultOptionWith = option.DefaultWith(() =\u003e 99);\n// ^-- useful if creating the value is costly\n\n//\n// Working with Result type\npublic Result\u003cint, string\u003e TryDivide(\n    int numerator,\n    int denominator) =\u003e\n    denominator == 0\n        ? Result\u003cint, string\u003e.Error(\"Cannot divide by zero\")\n        : Result\u003cint, string\u003e.Ok(numerator / denominator);\n\nTryDivide(10, 2)\n    .Match(\n        ok: x =\u003e Console.WriteLine(\"Result: {0}\", x),\n        error: e =\u003e Console.WriteLine(\"Error: {0}\", e));\n```\n\n## Option\n\nOptions have an underlying type and can optionally hold a value of that type. Options are a much safer way to handle nullable values, they virtually eliminate null reference exceptions. They also provide a fantastic means of reducing primitive congestion in your code.\n\n### Creating Options\n\n```csharp\nvar option = Option\u003cint\u003e.Some(5);\n\n// or, with type inference\nvar optionInferred = Option.Some(5);\n\n// or, with no value\nvar optionNone = Option\u003cint\u003e.NoneValue;\n\n// also returns none\nvar optionNull = Option\u003cobject\u003e.Some(default!);\n```\n\n### Using Option\n\nOptions are commonly used when a operation might not return a value. For example, the method below tries to find a number in a list that satisfies a predicate. If the number is found, it is returned as a `Some`, otherwise, `None` is returned.\n\n```csharp\nusing Danom;\n\npublic Option\u003cint\u003e TryFind(IEnumerable\u003cint\u003e numbers, Func\u003cint, bool\u003e predicate) =\u003e\n    numbers.FirstOrDefault(predicate).ToOption();\n```\n\nWith this method defined we can begin performing operations against the Option result:\n\n```csharp\nusing Danom;\n\nIEnumerable\u003cint\u003e nums = [1,2,3];\n\n// Exhaustive matching\nTryFind(nums, x =\u003e x == 1)\n    .Match(\n        some: x =\u003e Console.WriteLine(\"Found: {0}\", x),\n        none: () =\u003e Console.WriteLine(\"Did not find number\"));\n\n// Mapping the value (i.e., I want to access the value)\nOption\u003cint\u003e optionSum =\n    TryFind(nums, x =\u003e x == 1)\n        .Map(x =\u003e x + 1);\n\n// Binding the option (i.e., when a nested operation also returns an Option)\nOption\u003cint\u003e optionBindSum =\n    TryFind(nums, x =\u003e x == 1)\n        .Bind(num1 =\u003e\n            TryFind(nums, x =\u003e x == 2)\n                .Map(num2 =\u003e num1 + num2));\n\n// Handling \"None\"\nOption\u003cint\u003e optionDefault =\n    TryFind(nums, x =\u003e x == 4)\n        .DefaultValue(99);\n\nOption\u003cint\u003e optionDefaultWith =\n    TryFind(nums, x =\u003e x == 4)\n        .DefaultWith(() =\u003e 99); // useful if creating the value is expensive\n\nOption\u003cint\u003e optionOrElse =\n    TryFind(nums, x =\u003e x == 4)\n        .OrElse(Option\u003cint\u003e.Some(99));\n\nOption\u003cint\u003e optionOrElseWith =\n    TryFind(nums, x =\u003e x == 4)\n        .OrElseWith(() =\u003e Option\u003cint\u003e.Some(99)); // useful if creating the value is expensive\n```\n\n## Result\n\nResults are used to represent a success or failure outcome. They provide a more concrete way to manage the expected errors of an operation, then throwing exceptions. Especially in recoverable or reportable scenarios.\n\n### Creating Results\n\n```csharp\nusing Danom;\n\nvar result = Result\u003cint, string\u003e.Ok(5);\n\n// or, with an error\nvar resultError = Result\u003cint, string\u003e.Error(\"An error occurred\");\n```\n\n### Built-in Error Type\n\nDanom provides a built-in error type, `ResultErrors`, to simplify the creation of results with multiple errors. This type can be initialized with a single string, a collection of strings, or a key-value pair. It can be thought of as a domain-specific dictionary of string keys and N string values.\n\n```csharp\nusing Danom;\n\nvar resultErrors = Result\u003cint\u003e.Ok(5);\n\nvar resultErrorsError =\n    Result\u003cint\u003e.Error(new(\"An error occurred\"));\n\nvar resultErrorsMultiError =\n    Result\u003cint\u003e.Error(new([\"An error occurred\", \"Another error occurred\"]));\n\nvar resultErrorsTyped =\n    Result\u003cint\u003e.Error(new(\"error-key\", \"An error occurred\"));\n\nvar resultErrorsTyped =\n    Result\u003cint\u003e.Error(new(\"error-key\", [\"An error occurred\", \"Another error occurred\"]));\n\n```\n\n### Using Results\n\nResults are commonly used when an operation might not succeed, and you want to manage or report back the _expected_ errors. For example:\n\nLet's create a simple inline function to divide two numbers. If the denominator is zero, we want to return an error message.\n\n```csharp\nusing Danom;\n\nResult\u003cint, string\u003e TryDivide(int numerator, int denominator) =\u003e\n    denominator == 0\n        ? Result\u003cint, string\u003e.Error(\"Cannot divide by zero\")\n        : Result\u003cint, string\u003e.Ok(numerator / denominator);\n```\n\nWith this method defined we can begin performing operations against the result:\n\n```csharp\nusing Danom;\n\n// Exhaustive matching\nTryDivide(10, 2)\n    .Match(\n        ok: x =\u003e Console.WriteLine(\"Result: {0}\", x),\n        error: e =\u003e Console.WriteLine(\"Error: {0}\", e));\n\n// Mapping the value\nResult\u003cint, string\u003e resultSum =\n    TryDivide(10, 2)\n        .Map(x =\u003e x + 1);\n\n// Binding the result (i.e., when a nested operation also returns a Result)\nResult\u003cint, string\u003e resultBindSum =\n    TryDivide(10, 2)\n        .Bind(num1 =\u003e\n            TryDivide(20, 2)\n                .Map(num2 =\u003e\n                    num1 + num2));\n\n// Handling errors\nResult\u003cint, string\u003e resultDefault =\n    TryDivide(10, 0)\n        .DefaultValue(99);\n\nResult\u003cint, string\u003e resultDefaultWith =\n    TryDivide(10, 0)\n        .DefaultWith(() =\u003e 99); // useful if creating the value is expensive\n\nResult\u003cint, string\u003e resultOrElse =\n    TryDivide(10, 0)\n        .OrElse(Result\u003cint, string\u003e.Ok(99));\n\nResult\u003cint, string\u003e resultOrElseWith =\n    TryDivide(10, 0)\n        .OrElseWith(() =\u003e\n            Result\u003cint, string\u003e.Ok(99)); // useful if creating the value is expensive\n```\n\n### Result Errors\n\nSince error messages are frequently represented as keyed string collections, the `ResultErrors` type is provided to simplify Result creation. The flexible constructor allows errors to be initialized with a single string, a collection of strings, or a key-value pair.\n\n```csharp\nusing Danom;\n\nvar resultErrors =\n    Result\u003cint\u003e.Ok(5);\n\nvar resultErrorsError =\n    Result\u003cint\u003e.Error(new(\"An error occurred\"));\n\nvar resultErrorsMultiError =\n    Result\u003cint\u003e.Error(new([\"An error occurred\", \"Another error occurred\"]));\n\nvar resultErrorsTyped =\n    Result\u003cint\u003e.Error(new ResultErrors(\"error-key\", \"An error occurred\"));\n```\n\n## Procedural Programming\n\nInevitably you'll need to interact with these functional types in a procedural way. Both [Option](#option-tryget) and [Result](#result) provide a `TryGet` method to retrieve the underlying value. This method will return a `bool` indicating whether the value was successfully retrieved and the value itself as an output parameter.\n\n### Option TryGet\n\n```csharp\nusing Danom;\n\nvar option = Option\u003cint\u003e.Some(5);\n\nif (option.TryGet(out var value)) {\n    Console.WriteLine(\"Value: {0}\", value);\n}\nelse {\n    Console.WriteLine(\"No value\");\n}\n```\n\n\n### Result TryGet\n\n```csharp\nusing Danom;\n\nvar result = Result\u003cint, string\u003e.Ok(5);\n\nif (result.TryGet(out var value)) {\n    Console.WriteLine(\"Result: {0}\", value);\n}\nelse if (result.TryGetError(out var error)) {\n    Console.WriteLine(\"Error: {0}\", error);\n}\nelse {\n    Console.WriteLine(\"No value or error\");\n}\n```\n\n## String Parsing\n\nMost applications will at some point need to parse strings into primitives and value types. This is especially true when working with external data sources.\n\n`Option` provides a natural mechanism to handle the case where the string cannot be parsed. The \"TryParse\" API is provided to simplify the process of parsing strings into .NET primitives and value types.\n\n```csharp\nusing Danom;\n\n// a common pattern\nvar x = int.TryParse(\"123\", out var y) ? Option\u003cint\u003e.Some(y) : Option\u003cint\u003e.NoneValue;\n\n// or, more simply using the TryParse API\nvar myInt = intOption.TryParse(\"123\"); // -\u003e Some(123)\nvar myDouble = doubleOption.TryParse(\"123.45\"); // -\u003e Some(123.45)\nvar myBool = boolOption.TryParse(\"true\"); // -\u003e Some(true)\n\n// if the string cannot be parsed\nvar myIntNone = intOption.TryParse(\"danom\"); // -\u003e None\nvar myDoubleNone = doubleOption.TryParse(\"danom\"); // -\u003e None\nvar myBoolNone = boolOption.TryParse(\"danom\"); // -\u003e None\n\n// null strings are treated as None\nvar myIntNull = intOption.TryParse(null); // -\u003e None\n```\n\nThe full API is below:\n\n```csharp\npublic static class boolOption {\n    public static Option\u003cbool\u003e TryParse(string? x); }\n\npublic static class byteOption {\n    public static Option\u003cbyte\u003e TryParse(string? x, IFormatProvider? provider = null); }\n\npublic static class shortOption {\n    public static Option\u003cshort\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cshort\u003e TryParse(string? x); }\n\npublic static class intOption {\n    public static Option\u003cint\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cint\u003e TryParse(string? x); }\n\npublic static class longOption {\n    public static Option\u003clong\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003clong\u003e TryParse(string? x); }\n\npublic static class decimalOption {\n    public static Option\u003cdecimal\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cdecimal\u003e TryParse(string? x); }\n\npublic static class doubleOption {\n    public static Option\u003cdouble\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cdouble\u003e TryParse(string? x); }\n\npublic static class floatOption {\n    public static Option\u003cfloat\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cfloat\u003e TryParse(string? x); }\n\npublic static class GuidOption {\n    public static Option\u003cGuid\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cGuid\u003e TryParse(string? x);\n    public static Option\u003cGuid\u003e TryParseExact(string? x, string? format); }\n\npublic static class DateTimeOffsetOption {\n    public static Option\u003cDateTimeOffset\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cDateTimeOffset\u003e TryParse(string? x);\n    public static Option\u003cDateTimeOffset\u003e TryParseExact(string? x, string? format, IFormatProvider? provider = null, DateTimeStyles dateTimeStyles = DateTimeStyles.None); }\n\npublic static class DateTimeOption {\n    public static Option\u003cDateTime\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cDateTime\u003e TryParse(string? x);\n    public static Option\u003cDateTime\u003e TryParseExact(string? x, string? format, IFormatProvider? provider = null, DateTimeStyles dateTimeStyles = DateTimeStyles.None); }\n\npublic static class DateOnlyOption {\n    public static Option\u003cDateOnly\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cDateOnly\u003e TryParse(string? x);\n    public static Option\u003cDateOnly\u003e TryParseExact(string? x, string? format, IFormatProvider? provider = null, DateTimeStyles dateTimeStyles = DateTimeStyles.None); }\n\npublic static class TimeOnlyOption {\n    public static Option\u003cTimeOnly\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cTimeOnly\u003e TryParse(string? x);\n    public static Option\u003cTimeOnly\u003e TryParseExact(string? x, string? format, IFormatProvider? provider = null, DateTimeStyles dateTimeStyles = DateTimeStyles.None); }\n\npublic static class TimeSpanOption {\n    public static Option\u003cTimeSpan\u003e TryParse(string? x, IFormatProvider? provider = null);\n    public static Option\u003cTimeSpan\u003e TryParse(string? x);\n    public static Option\u003cTimeSpan\u003e TryParseExact(string? x, string? format, IFormatProvider? provider = null); }\n\npublic static class EnumOption {\n    public static Option\u003cTEnum\u003e TryParse\u003cTEnum\u003e(string? x) where TEnum : struct; }\n```\n\n## Integrations\n\nSince Danom introduces types that are most commonly found in your model and business logic layers, external integrations are not only inevitable but required to provide a seamless experience when building applications.\n\nThese are completely optional, but provide a great way to integrate Danom with your codebase.\n\n### Fluent Validation Integration\n\n[Fluent Validation](https://fluentvalidation.net/) is an excellent library for building validation rules for your models. A first-class integration is available via [Danom.Validation](src/Danom.Validation/README.md) to provide a seamless way to validate your models and return a `Result` with the validation errors.\n\nA quick example:\n\n```csharp\nusing Danom;\nusing Danom.Validation;\nusing FluentValidation;\n\npublic record Person(\n    string Name,\n    Option\u003cstring\u003e Email);\n\npublic class PersonValidator\n    : AbstractValidator\u003cPerson\u003e {\n    public PersonValidator()\n    {\n        RuleFor(x =\u003e x.Name).NotEmpty();\n        RuleFor(x =\u003e x.Email).Optional(x =\u003e x.EmailAddress());\n    }\n}\n\nvar result =\n    ValidationResult\u003cPerson\u003e\n        .From\u003cPersonValidator\u003e(new(\n            Name: \"John Doe\",\n            Email: Option.Some(\"john@doe.com\")));\n\nresult.Match(\n    x =\u003e Console.WriteLine(\"Input is valid: {0}\", x),\n    e =\u003e Console.WriteLine(\"Input is invalid: {0}\", e));\n```\n\nDocumentation can be found [here](src/Danom.Validation/README.md).\n\n### ASP.NET Core MVC \u0026 Razor Pages Integration\n\nDanom is integrated with ASP.NET Core MVC via [Danom.Mvc](src/Danom.Mvc/README.md). This library provides a set of utilities to help integrate the core types with common tasks in ASP.NET Core MVC applications.\n\n### ASP.NET Core Minimal API Integration\n\n\u003e Coming soon\n\n## Contribute\n\nThank you for considering contributing to Danom, and to those who have already contributed! We appreciate (and actively resolve) PRs of all shapes and sizes.\n\nWe kindly ask that before submitting a pull request, you first submit an [issue](https://github.com/pimbrouwers/Danom/issues) or open a [discussion](https://github.com/pimbrouwers/Danom/discussions).\n\nIf functionality is added to the API, or changed, please kindly update the relevant [document](https://github.com/pimbrouwers/Danom/tree/master/docs). Unit tests must also be added and/or updated before a pull request can be successfully merged.\n\nOnly pull requests which pass all build checks and comply with the general coding guidelines can be approved.\n\nIf you have any further questions, submit an [issue](https://github.com/pimbrouwers/Danom/issues) or open a [discussion](https://github.com/pimbrouwers/Danom/discussions).\n\n\n## Find a bug?\n\nThere's an [issue](https://github.com/pimbrouwers/Danom/issues) for that.\n\n## License\n\nLicensed under [MIT](https://github.com/pimbrouwers/Danom/blob/master/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimbrouwers%2Fdanom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpimbrouwers%2Fdanom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimbrouwers%2Fdanom/lists"}