{"id":13629256,"url":"https://github.com/Ne4to/N.SourceGenerators.UnionTypes","last_synced_at":"2025-04-17T08:34:36.896Z","repository":{"id":65553415,"uuid":"588649919","full_name":"Ne4to/N.SourceGenerators.UnionTypes","owner":"Ne4to","description":"Discriminated union type source generator","archived":false,"fork":false,"pushed_at":"2024-05-02T14:21:46.000Z","size":217,"stargazers_count":10,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-08T20:45:09.529Z","etag":null,"topics":["csharp-sourcegenerator"],"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/Ne4to.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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":"2023-01-13T16:38:05.000Z","updated_at":"2024-05-07T22:48:41.000Z","dependencies_parsed_at":"2023-10-02T13:12:21.806Z","dependency_job_id":"1efdb58e-72d8-4a17-ae3d-0088e260705f","html_url":"https://github.com/Ne4to/N.SourceGenerators.UnionTypes","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ne4to%2FN.SourceGenerators.UnionTypes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ne4to%2FN.SourceGenerators.UnionTypes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ne4to%2FN.SourceGenerators.UnionTypes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ne4to%2FN.SourceGenerators.UnionTypes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ne4to","download_url":"https://codeload.github.com/Ne4to/N.SourceGenerators.UnionTypes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249326186,"owners_count":21251735,"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":["csharp-sourcegenerator"],"created_at":"2024-08-01T22:01:05.891Z","updated_at":"2025-04-17T08:34:36.561Z","avatar_url":"https://github.com/Ne4to.png","language":"C#","readme":"# N.SourceGenerators.UnionTypes\nDiscriminated union type source generator\n\n## Motivation\nC# doesn't support discriminated unions yet. This source generator helps automate writing union types with set of helper methods.\n\n## Getting Started\nAdd package reference to `N.SourceGenerators.UnionTypes`\n```shell\ndotnet add package N.SourceGenerators.UnionTypes\n```\nCreate a partial class or struct that will be used as a union type\n```csharp\npublic partial class FooResult\n{\n}\n```\nAdd types you want to use in a discriminated union\n```csharp\npublic record Success(int Value);\npublic record ValidationError(string Message);\npublic record NotFoundError;\n\npublic partial class FooResult\n{\n}\n```\nAdd `N.SourceGenerators.UnionTypes.UnionTypeAttribute` to a union type.\n```csharp\nusing N.SourceGenerators.UnionTypes;\n\npublic record Success(int Value);\npublic record ValidationError(string Message);\npublic record NotFoundError;\n\n[UnionType(typeof(Success))]\n[UnionType(typeof(ValidationError))]\n[UnionType(typeof(NotFoundError))]\npublic partial class FooResult\n{\n}\n```\nOr you can use generic type.\n```csharp\npublic partial class OperationDataResult\u003c[GenericUnionType] TResult, [GenericUnionType] TError\u003e\n{\n}\n\n// extend generic type union with additional Int32 type\n[UnionType(typeof(int))]\npublic partial class ExtendedOperationDataResult\u003c[GenericUnionType] TResult, [GenericUnionType] TError\u003e\n{\n}\n```\nNull values are not allowed by default. This behavior can be overriden by `AllowNull = true` parameter.\n```csharp\n[UnionType(typeof(int?), AllowNull = true)]\n[UnionType(typeof(string), AllowNull = true)]\npublic partial class ResultNullable\u003c[GenericUnionType(AllowNull = true)] T\u003e\n{\n}\n```\n\n## Examples\n\nAll examples can be found in [examples project](https://github.com/Ne4to/N.SourceGenerators.UnionTypes/blob/main/examples/N.SourceGenerators.UnionTypes.Examples/Program.cs)\n\n### Basic\n\nImplicit conversion\n```csharp\npublic FooResult ImplicitReturn()\n{\n    // you can return any union type variation without creating FooResult\n    return new NotFoundError();\n}\n```\nExplicit conversion\n```csharp\npublic ValidationError ExplicitCast(FooResult result)\n{\n    return (ValidationError)result;\n}\n```\nChecking value type\n```csharp\npublic void ValueTypeProperty()\n{\n    FooResult foo = GetFoo();\n    Type valueType = foo.ValueType; // returns typeof(NotFoundError)\n\n    static FooResult GetFoo()\n    {\n        return new NotFoundError();\n    }\n}\n```\nTryGet method is used to check if union contains a specific type\n```csharp\npublic void TryGetValue()\n{\n    FooResult foo = GetFoo();\n    if (foo.TryGetNotFoundError(out var notFoundError))\n    {\n        // make something with notFoundError\n    }\n\n    static FooResult GetFoo()\n    {\n        return new NotFoundError();\n    }\n}\n```\nAlias for each variant is generated based on type name. Use alias parameter to override it.\n```csharp\n[UnionType(typeof(int))]\n[UnionType(typeof(string))]\n// default alias is 'ArrayOfTupleOfIntAndString' but it is overriden by alias parameter\n[UnionType(typeof(Tuple\u003cint,string\u003e[]), alias: \"Items\")]\npublic partial class AliasResult\n{\n}\n```\n\n### Handle all variants\n\nMatch and MatchAsync methods are used to convert union type to another type. These methods force you to handle all possible variations.\n```csharp\npublic IActionResult MatchMethod(FooResult result)\n{\n    return result.Match\u003cIActionResult\u003e(\n        success =\u003e new OkResult(),\n        validationError =\u003e new BadRequestResult(),\n        notFoundError =\u003e new NotFoundResult()\n    );\n}\n\npublic async Task\u003cIActionResult\u003e MatchAsyncMethod(FooResult result, CancellationToken cancellationToken)\n{\n    return await result.MatchAsync\u003cIActionResult\u003e(\n        static async (success, ct) =\u003e\n        {\n            await SomeWork(success, ct);\n            return new OkResult();\n        }, static async (validationError, ct) =\u003e\n        {\n            await SomeWork(validationError, ct);\n            return new BadRequestResult();\n        }, static async (notFoundError, ct) =\u003e\n        {\n            await SomeWork(notFoundError, ct);\n            return new NotFoundResult();\n        }, cancellationToken);\n\n    static Task SomeWork\u003cT\u003e(T value, CancellationToken ct)\n    {\n        return Task.Delay(100, ct);\n    }\n}\n```\nSwitch and SwitchAsync methods are used to execute some work based on inner type\n```csharp\n public void SwitchMethod(FooResult result)\n{\n    result.Switch(\n        success =\u003e SomeWork(success),\n        validationError =\u003e SomeWork(validationError),\n        notFoundError =\u003e SomeWork(notFoundError)\n    );\n\n    static void SomeWork\u003cT\u003e(T value)\n    {\n        throw new NotImplementedException();\n    }\n}\n\npublic async Task SwitchAsyncMethod(FooResult result, CancellationToken cancellationToken)\n{\n    await result.SwitchAsync(\n        static async (success, ct) =\u003e\n        {\n            await SomeWork(success, ct);\n        }, static async (validationError, ct) =\u003e\n        {\n            await SomeWork(validationError, ct);\n        }, static async (notFoundError, ct) =\u003e\n        {\n            await SomeWork(notFoundError, ct);\n        }, cancellationToken);\n\n    static Task SomeWork\u003cT\u003e(T value, CancellationToken ct)\n    {\n        return Task.Delay(100, ct);\n    }\n}\n```\n\n### JSON serialization (EXPERIMENTAL)\n\nTo add JSON support\n- add `JsonPolymorphicUnion` attribute to union type\n- add `TypeDiscriminator` to each type variant\n\n#### Limitations:\n- .NET 7 or newer\n- only complex type variants\n\n#### Example\n```csharp\n[UnionType(typeof(JsonTestsFooJ), TypeDiscriminator = \"Foo\")]\n[UnionType(typeof(JsonTestsBarJ), TypeDiscriminator = \"Bar\")]\n[JsonPolymorphicUnion]\npublic partial class JsonTestsUnion\n{\n}\n```\n\n### Union to union converter\n\nWhen one union type's variants is subset of another union type's variants use one of the following attributes to convert one type to another: `UnionConverterTo`, `UnionConverterFrom`, or `UnionConverter`.\n\n```csharp\n[UnionConverterFrom(typeof(DataAccessResult))] // use this attribute\npublic partial class BusinessLogicResult\n{\n}\n\n[UnionConverterTo(typeof(BusinessLogicResult))] // OR this\npublic partial class DataAccessResult\n{\n}\n\n[UnionConverter(typeof(DataAccessResult), typeof(BusinessLogicResult))] // OR this\npublic static partial class Converters\n{\n}\n\npublic class Repository\n{\n    public DataAccessResult UpdateItem()\n    {\n        return new NotFoundError();\n    }\n}\n\npublic class Service\n{\n    private readonly Repository _repository;\n\n    public BusinessLogicResult Update()\n    {\n        var isValid = IsValid();\n        if (!isValid)\n        {\n            return new ValidationError(\"the item is not valid\");\n        }\n\n        var repositoryResult = _repository.UpdateItem();\n        // implicit conversion DataAccessResult to BusinessLogicResult when `UnionConverterTo` or `UnionConverterFrom` attribute is used\n        return repositoryResult;\n        // OR extension method when UnionConverter attribute is used\n        return repositoryResult.Convert();\n    }\n\n    private bool IsValid() =\u003e throw new NotImplementedException();\n}\n```\n\n## Configuration\n|Property|Default|Description|\n|--|--|--|\n|UnionTypesGenerator_ExcludeFromCodeCoverage|true|Add [ExcludeFromCodeCoverage]([https://](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute?view=net-8.0)) attribute when `true`|\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\"\u003e\n    \u003cPropertyGroup\u003e\n        \u003cUnionTypesGenerator_ExcludeFromCodeCoverage\u003efalse\u003c/UnionTypesGenerator_ExcludeFromCodeCoverage\u003e\n    \u003c/PropertyGroup\u003e\n\u003c/Project\u003e\n```","funding_links":[],"categories":["Content","Source Generators"],"sub_categories":["81. [N.SourceGenerators.UnionTypes](https://ignatandrei.github.io/RSCG_Examples/v2/docs/N.SourceGenerators.UnionTypes) , in the [FunctionalProgramming](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#functionalprogramming) category","Functional Programming"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNe4to%2FN.SourceGenerators.UnionTypes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNe4to%2FN.SourceGenerators.UnionTypes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNe4to%2FN.SourceGenerators.UnionTypes/lists"}