Base16.ErrGen 0.0.6

dotnet add package Base16.ErrGen --version 0.0.6
                    
NuGet\Install-Package Base16.ErrGen -Version 0.0.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Base16.ErrGen" Version="0.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Base16.ErrGen" Version="0.0.6" />
                    
Directory.Packages.props
<PackageReference Include="Base16.ErrGen" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Base16.ErrGen --version 0.0.6
                    
#r "nuget: Base16.ErrGen, 0.0.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Base16.ErrGen@0.0.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Base16.ErrGen&version=0.0.6
                    
Install as a Cake Addin
#tool nuget:?package=Base16.ErrGen&version=0.0.6
                    
Install as a Cake Tool

Base16.ErrGen

A C# source generator that generates structured error types from [Error] attribute templates. No runtime dependency required — all types are source-generated.

Supported Frameworks

  • .NET 5.0+

Installation

dotnet add package Base16.ErrGen

Usage

Define a partial record struct or partial record decorated with one or more [Error] attributes. Each attribute takes a message template string:

[Error("User '{Name:String}' was not found")]
[Error("User with ID {Id:Int32} was not found")]
public partial record struct UserNotFoundError;

Base16.ErrGen generates a Message property, typed properties for each template argument, and static factory methods:

// Generated code
public partial record struct UserNotFoundError
{
    public String Message { get; private init; } = default!;
    public String Name { get; private init; } = default!;
    public Int32 Id { get; private init; } = default!;

    public UserNotFoundError() { }

    public static UserNotFoundError FromName(String name)
    {
        var message = String.Concat(
            "User '",
            name,
            "' was not found"
        );

        return new UserNotFoundError()
        {
            Message = message,
            Name = name,
        };
    }

    public static UserNotFoundError FromId(Int32 id)
    {
        var message = String.Concat(
            "User with ID ",
            id,
            " was not found"
        );

        return new UserNotFoundError()
        {
            Message = message,
            Id = id,
        };
    }
}

You can then create errors using the generated factory methods:

var error = UserNotFoundError.FromName("alice");
Console.WriteLine(error.Message); // User 'alice' was not found
Console.WriteLine(error.Name);    // alice

var error2 = UserNotFoundError.FromId(42);
Console.WriteLine(error2.Message); // User with ID 42 was not found
Console.WriteLine(error2.Id);      // 42

Record Structs vs Record Classes

The [Error] attribute works on both partial record struct and partial record (class) types:

// Record struct — value type, public parameterless constructor
[Error("Validation failed for '{Field:String}'")]
public partial record struct ValidationError;

// Record class — reference type, private parameterless constructor
[Error("Payment of {Amount:Decimal} failed")]
public partial record PaymentError;

The generated code differs slightly: record structs get a public parameterless constructor, while record classes get a private one.

Access Modifiers

Both public and internal error types are supported:

[Error("An unexpected error occurred: {Details:String}")]
internal partial record struct InternalError;

Template Syntax

Templates use {Name} or {Name:Type} placeholders:

Placeholder Factory parameter Description
{Name} Object? name Untyped argument (defaults to Object?)
{Name:String} String name Typed argument

Multiple arguments in a single template produce a factory method named From{Arg1}{Arg2}And{ArgN}:

[Error("{Method:String} {Path:String} returned {StatusCode:Int32}")]
public partial record struct HttpError;

// Generates:
// public static HttpError FromMethodPathAndStatusCode(
//     String method, String path, Int32 statusCode)

Multiple Error Templates

Applying multiple [Error] attributes to a single type generates multiple factory methods:

[Error("Connection to '{Host:String}' timed out after {TimeoutMs:Int32}ms")]
[Error("Connection to '{Host:String}' was refused")]
public partial record struct ConnectionError;

// Generates:
// public static ConnectionError FromHostAndTimeoutMs(String host, Int32 timeoutMs)
// public static ConnectionError FromHost(String host)

Note that with two arguments, the naming is From{Arg1}And{Arg2} — the "And" separator only appears before the last argument.

Base Type Inheritance

Use the assembly-level [ErrorBaseType] attribute to make all generated error types inherit from a shared interface or class:

public interface IError
{
    String Message { get; }
}

[assembly: ErrorBaseType(typeof(IError))]

All [Error] types in the assembly will now implement IError:

[Error("User '{Name:String}' was not found")]
public partial record struct UserNotFoundError; // implements IError

[Error("Payment of {Amount:Decimal} failed")]
public partial record PaymentError; // implements IError

If the base type declares a String Message property, the generator skips generating it on the error types (since it's satisfied by the base type).

Both interfaces and classes are supported. Classes work with record classes only — record structs cannot inherit from classes.

// Interface — works with both record structs and record classes
[assembly: ErrorBaseType(typeof(IError))]

// Class — works with record classes only
[assembly: ErrorBaseType(typeof(BaseError))]

Abstract Records with Positional Parameters

Abstract records with positional parameters are supported as base types. The generator chains to the base constructor automatically:

public abstract record Error(String Message);

[assembly: ErrorBaseType(typeof(Error))]

[Error("Something went wrong")]
public partial record MyError;

The generated constructor chains to the base record's constructor:

public partial record MyError : global::Error
{
    private MyError(String message) : base(message) { }

    public static MyError From()
    {
        var message = "Something went wrong";
        return new MyError(message);
    }
}

If the base record has additional constructor parameters beyond Message, they are automatically included as the first parameters in all factory methods:

public abstract record Error(String Message);
public abstract record TracedError(String Message, Guid TraceId) : Error(Message);

[assembly: ErrorBaseType(typeof(TracedError))]

[Error("Authorization failed for user '{UserId:String}'")]
public partial record AuthError;

// Generated factory method:
// public static AuthError FromUserId(Guid traceId, String userId)
//
// Usage:
var error = AuthError.FromUserId(Guid.NewGuid(), "user-42");

Explicit Per-Record Base Type

You can override the assembly-level ErrorBaseType for individual error records by declaring the base type directly:

public abstract record Error(String Message);
public abstract record TracedError(String Message, Guid TraceId) : Error(Message);

[assembly: ErrorBaseType(typeof(Error))]

// Uses Error (from assembly-level ErrorBaseType)
[Error("Payment of {Amount:Decimal} failed")]
public partial record PaymentError;

// Uses TracedError (explicitly declared, overrides assembly-level)
[Error("Authorization failed for user '{UserId:String}'")]
public partial record AuthError : TracedError;

This also works without any [ErrorBaseType] attribute — just declare the base type on the record:

public abstract record Error(String Message);

[Error("Database connection to '{Host:String}' failed")]
public partial record DatabaseError : Error;

Usage with OneOf

Generated error types pair naturally with OneOf for discriminated-union-style return types:

using OneOf;

[Error("User '{Name:String}' was not found")]
public partial record struct UserNotFoundError;

[Error("Field '{FieldName:String}' must be between {Min:Int32} and {Max:Int32}")]
public partial record struct ValidationError;

public static OneOf<User, ValidationError, UserNotFoundError> CreateUser(String name, String email)
{
    if (String.IsNullOrWhiteSpace(name))
        return ValidationError.FromFieldNameMinAndMax("Name", 1, 100);

    return new User(name, email);
}

Handle results exhaustively with Match:

var result = CreateUser("", "bob@example.com");
var message = result.Match(
    user => $"Created user: {user.Name}",
    validationErr => $"Validation failed: {validationErr.Message}",
    notFoundErr => $"Not found: {notFoundErr.Message}"
);

Or check for specific error types with TryPickT and IsT0/AsT1:

if (result.TryPickT1(out var validationErr, out _))
{
    Console.WriteLine(validationErr.Message);
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.6 491 3/14/2026
0.0.5 105 3/13/2026
0.0.4 105 3/13/2026
0.0.3 96 3/12/2026
0.0.2 96 3/12/2026
0.0.1 98 3/12/2026