An open API service indexing awesome lists of open source software.

https://github.com/altasoft/choice

AltaSoft.Choice provides the [Choice] attribute to define discriminated union types in C#, enabling source-generated, type-safe choice models.
https://github.com/altasoft/choice

Last synced: 2 months ago
JSON representation

AltaSoft.Choice provides the [Choice] attribute to define discriminated union types in C#, enabling source-generated, type-safe choice models.

Awesome Lists containing this project

README

          

# AltaSoft.Choice

[![NuGet - AltaSoft.Choice](https://img.shields.io/nuget/v/AltaSoft.Choice?label=AltaSoft.Choice)](https://www.nuget.org/packages/AltaSoft.Choice)
[![NuGet - AltaSoft.Choice.Generator](https://img.shields.io/nuget/v/AltaSoft.Choice.Generator?label=AltaSoft.Choice.Generator)](https://www.nuget.org/packages/AltaSoft.Choice.Generator)
[![Dot NET 8+](https://img.shields.io/static/v1?label=DOTNET&message=8%2B&color=0c3c60&style=for-the-badge)](https://dotnet.microsoft.com)

**AltaSoft.ChoiceGenerator** is a lightweight C# source generator that allows you to define *choice types* (discriminated unions) with minimal syntax.

---

## ✨ Features

- Simple `[Choice]` attribute for defining alternatives
- Generates type-safe properties
- Supports XML and System.Text.Json serialization
- Includes `CreateAsXxx`, `Match`, and `Switch` methods
- Auto-generates enum for valid choice types
- Implicit conversion operators for easy usage
- Generates XmlSerializer

---

## 🛠️ Installation

Add the following NuGet packages to your project:

```xml


```

---

## ✅ Define Your Choice Type

Mark your class with `[Choice]` and define **partial nullable properties** :

```csharp
using AltaSoft.Choice;
namespace AltaSoft.ChoiceGenerator.Tests;

[Choice]
public sealed partial class Authorisation1Choice
{
///
/// Specifies the authorisation, in a coded form.
///
[XmlTag("Cd")]
[JsonPropertyName("cd")]
public partial Authorisation1Code? Code { get; set; }

///
/// Specifies the authorisation, in a free text form.
///
[XmlTag("Prtry")]
public partial Proprietary? Proprietary { get; set; }
}
```

---

## ⚙️ Generated Code

Below is the generated code for the example above:

```csharp
//------------------------------------------------------------------------------
//
// This code was generated by 'AltaSoft Choice.Generator'.
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
//
//------------------------------------------------------------------------------

#nullable enable

using AltaSoft.ChoiceGenerator.Tests;
using AltaSoft.Choice;
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;

namespace AltaSoft.ChoiceGenerator.Tests;

#pragma warning disable CS8774 // Member must have a non-null value when exiting.
#pragma warning disable CS0628 // New protected member declared in sealed type

public sealed partial record Authorisation1Choice
{
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public Authorisation1Choice()
{
}

///
/// Choice enum
///
[JsonIgnore]
[XmlIgnore]
public ChoiceOf ChoiceType { get; private set; }

private AltaSoft.ChoiceGenerator.Tests.Authorisation1Code? _code;

///
/// Specifies the authorisation, in a coded form.
///
[DisallowNull]
[XmlElement("Cd")]
public partial AltaSoft.ChoiceGenerator.Tests.Authorisation1Code? Code
{
get => _code;
set
{
_code = value ?? throw new InvalidOperationException("Choice value cannot be null");
_proprietary = null;
ChoiceType = ChoiceOf.Code;
}
}

private Proprietary? _proprietary;

///
/// Specifies the authorisation, in a free text form.
///
[DisallowNull]
[XmlElement("Prtry")]
public partial Proprietary? Proprietary
{
get => _proprietary;
set
{
_proprietary = value ?? throw new InvalidOperationException("Choice value cannot be null");
_code = null;
ChoiceType = ChoiceOf.Proprietary;
}
}

///
/// Creates a new instance and sets its value using the specified .
///
/// The value to assign to the created choice instance.
public static AltaSoft.ChoiceGenerator.Tests.Authorisation1Choice CreateAsCode(AltaSoft.ChoiceGenerator.Tests.Authorisation1Code value) => new () { Code = value };

///
/// Creates a new instance and sets its value using the specified .
///
/// The value to assign to the created choice instance.
public static AltaSoft.ChoiceGenerator.Tests.Authorisation1Choice CreateAsProprietary(Proprietary value) => new () { Proprietary = value };

///
/// Applies the appropriate function based on the current choice type
///
/// The return type of the provided match functions
/// Function to invoke if the choice is a value
/// Function to invoke if the choice is a value
public TResult Match(
Func matchCode,
Func matchProprietary)
{
return ChoiceType switch
{
ChoiceOf.Code => matchCode(Code!.Value),
ChoiceOf.Proprietary => matchProprietary(Proprietary!),
_ => throw new InvalidOperationException($"Invalid ChoiceType. '{ChoiceType}'")
};
}

///
/// Applies the appropriate Action based on the current choice type
///
/// Action to invoke if the choice is a value
/// Action to invoke if the choice is a value
public void Switch(
Action matchCode,
Action matchProprietary)
{
switch (ChoiceType)
{
case ChoiceOf.Code:
matchCode(Code!.Value);
return;

case ChoiceOf.Proprietary:
matchProprietary(Proprietary!);
return;

default:
throw new XmlException($"Invalid ChoiceType. '{ChoiceType}'");
}
}

///
/// Implicitly converts an to an .
///
/// The to convert.
///
/// instance representing the code.
///
public static implicit operator Authorisation1Choice(AltaSoft.ChoiceGenerator.Tests.Authorisation1Code value) => CreateAsCode(value);

///
/// Implicitly converts an to an .
///
/// The to convert.
///
/// instance representing the code.
///
public static implicit operator Authorisation1Choice(Proprietary value) => CreateAsProprietary(value);

///
/// Determines whether the property should be serialized.
///
///
/// true if has a value; otherwise, false.
///
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeCode() => Code.HasValue;

///
/// Choice enumeration
///
[XmlType("ChoiceOf.Authorisation1Choice")]
public enum ChoiceOf
{
///
/// Specifies the authorisation, in a coded form.
///
Code,
///
/// Specifies the authorisation, in a free text form.
///
Proprietary,
}
}

```
---

## 💡 Example Usage

### Creating with CreateAs methods
```csharp
var choice = Authorisation1Choice.CreateAsCode(Authorisation1Code.FileLevelAuthorisationSummary);

var result = choice.Match(
code => $"It's a code: {code}",
prop => $"It's proprietary: {prop.ToString()}"
);

choice.Switch(
code => Console.WriteLine($"String: {code}"),
prop => Console.WriteLine($"Number: {prop.ToString()}")
);
```

### Creating using implicit operators

if property types are distinct implicit operators are generated
```csharp
Authosiration1Choice choice = Authorisation1Code.FileLevelAuthorisationSummary;
```

## 📦 Projects

- `AltaSoft.Choice`
Contains the `[Choice]` marker attribute

- `AltaSoft.Choice.Generator`
Implements the source generator that produces boilerplate code

---

## 📄 License

This project is licensed under the [MIT License](LICENSE).