TypedStateBuilder.Generator

RSCG – TypedStateBuilder.Generator    

name TypedStateBuilder.Generator
nuget https://www.nuget.org/packages/TypedStateBuilder.Generator/
link https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator
author Georgiy Petrov

Generate strongly typed state builders for C# applications, enforced at compile time

**Summary of TypedStateBuilder.Generator**

**Purpose:** Generates compile-time safe step-by-step builders using the *type-state pattern* — each required property must be set in order before Build() is available, enforced at compile time.

**NuGet:** https://www.nuget.org/packages/TypedStateBuilder.Generator/

**GitHub:** https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator

**Author:** Georgiy Petrov

**How to use:**

1. Decorate a builder class with [TypedStateBuilder], mark each required step with [StepForValue], and optionally add [ValidateValue] for validation:

“`csharp

[TypedStateBuilder]

public class PersonBuilder

{

[StepForValue]

[ValidateValue(nameof(ValidateName))]

private string lastName = string.Empty;

[StepForValue]

[ValidateValue(nameof(ValidateName))]

private string firstName = string.Empty;

public void ValidateName(string name)

{

if (string.IsNullOrWhiteSpace(name) || name.Length <= 1)

throw new ArgumentException(“Name must be at least 2 characters long.”);

}

[Build]

public Person Build() => new Person(firstName, lastName);

}

“`

2. Use the generated fluent builder — steps are enforced in order at compile time:

“`csharp

var p = TypedStateBuilders

.CreatePersonBuilder()

.SetFirstName(“Andrei”)

.SetLastName(“Ignat”)

.Build();

Console.WriteLine(p.FullName()); // “Andrei Ignat”

“`

   

This is how you can use TypedStateBuilder.Generator .

The code that you start with is


<project sdk="Microsoft.NET.Sdk">

  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>net10.0</targetframework>
	  <nullable>enable</nullable>
  </propertygroup>

	  <propertygroup>
        <emitcompilergeneratedfiles>true</emitcompilergeneratedfiles>
        <compilergeneratedfilesoutputpath>$(BaseIntermediateOutputPath)\GX</compilergeneratedfilesoutputpath>
    </propertygroup>

	  <itemgroup>
	    <packagereference outputitemtype="Analyzer" privateassets="all" version="1.2.0" include="TypedStateBuilder.Generator">
	  </packagereference>

	  
	 

</itemgroup>


The code that you will use is


using Builder;
using TypedStateBuilder;
Console.WriteLine("create person builder");

var p = TypedStateBuilders
    .CreatePersonBuilder()
    .SetFirstName("Andrei")
    .SetLastName("Ignat")
    .Build()
    ;
;

Console.WriteLine(p.FullName());



using TypedStateBuilder;
namespace Builder;

[TypedStateBuilder]
public class PersonBuilder
{
    
    [StepForValue]
    [ValidateValue(nameof(ValidateName))] 
    private string lastName = string.Empty;

    [StepForValue]
    [ValidateValue(nameof(ValidateName))]
    private string firstName = string.Empty;

    public void ValidateName(string name)
    {
        if (string.IsNullOrWhiteSpace(name) || name.Length &lt;= 1)
        {
            throw new ArgumentException("Name must be at least 2 characters long.", nameof(name));
        }
    }

    
    [Build]
    public Person Build()
     =&gt; new Person(firstName, lastName);
}

public record Person(string firstName, string lastName)
{
    public string FullName() =&gt; $"{firstName} {lastName}";
}

   The code that is generated is

// <auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Builder
{

file static class PersonBuilder_Accessors
{
    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    internal static extern global::Builder.PersonBuilder Create();

    [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "lastName")]
    internal static extern ref string SetLastNameField(global::Builder.PersonBuilder builder);

    [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "firstName")]
    internal static extern ref string SetFirstNameField(global::Builder.PersonBuilder builder);

    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ValidateName")]
    internal static extern void Validate_SetLastName_ValidateName_0(global::Builder.PersonBuilder owner, string value);

    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ValidateName")]
    internal static extern void Validate_SetFirstName_ValidateName_0(global::Builder.PersonBuilder owner, string value);

    [UnsafeAccessor(UnsafeAccessorKind.Method)]
    internal static extern global::Builder.Person Build(global::Builder.PersonBuilder builder);

}

public sealed class TypedPersonBuilder<tlastnamestate  , tfirstnamestate="">
    where TLastNameState : global::TypedStateBuilder.IValueState
    where TFirstNameState : global::TypedStateBuilder.IValueState
{
    private global::Builder.PersonBuilder Inner { get; }

    internal TypedPersonBuilder(global::Builder.PersonBuilder inner)
    {
        Inner = inner;
    }

    internal static TypedPersonBuilder<global::typedstatebuilder.valueset  , tfirstnamestate=""> SetLastNameCore<tfirstnamestate>(TypedPersonBuilder<global::typedstatebuilder.valueunset  , tfirstnamestate=""> builder, string value)
    where TFirstNameState : global::TypedStateBuilder.IValueState
    {
        PersonBuilder_Accessors.SetLastNameField(builder.Inner) = value;
        return new TypedPersonBuilder<global::typedstatebuilder.valueset  , tfirstnamestate="">(builder.Inner);
    }

    internal static TypedPersonBuilder<tlastnamestate  , global::typedstatebuilder.valueset=""> SetFirstNameCore<tlastnamestate>(TypedPersonBuilder<tlastnamestate  , global::typedstatebuilder.valueunset=""> builder, string value)
    where TLastNameState : global::TypedStateBuilder.IValueState
    {
        PersonBuilder_Accessors.SetFirstNameField(builder.Inner) = value;
        return new TypedPersonBuilder<tlastnamestate  , global::typedstatebuilder.valueset="">(builder.Inner);
    }

    internal static global::Builder.Person BuildCore(TypedPersonBuilder<global::typedstatebuilder.valueset  , global::typedstatebuilder.valueset=""> builder)
    {
        List<exception>? exceptions = null;

        try
        {
            PersonBuilder_Accessors.Validate_SetLastName_ValidateName_0(builder.Inner, PersonBuilder_Accessors.SetLastNameField(builder.Inner));
        }
        catch (Exception ex)
        {
            (exceptions ??= new List<exception>()).Add(ex);
        }

        try
        {
            PersonBuilder_Accessors.Validate_SetFirstName_ValidateName_0(builder.Inner, PersonBuilder_Accessors.SetFirstNameField(builder.Inner));
        }
        catch (Exception ex)
        {
            (exceptions ??= new List<exception>()).Add(ex);
        }

        if (exceptions is not null)
        {
            throw new AggregateException(exceptions);
        }

        return PersonBuilder_Accessors.Build(builder.Inner);
    }

}

public static partial class TypedPersonBuilderExtensions
{
    /// <summary>
    /// Sets the lastName.
    /// </summary>
    /// <param name="builder">The builder.
    /// <param name="value">The value for <c>lastName</c>.
    /// <returns>
    /// The updated builder.
    /// </returns>
    public static TypedPersonBuilder<global::typedstatebuilder.valueset  , tfirstnamestate=""> SetLastName<tfirstnamestate>(this TypedPersonBuilder<global::typedstatebuilder.valueunset  , tfirstnamestate=""> builder, string value)
    where TFirstNameState : global::TypedStateBuilder.IValueState
        =&gt; TypedPersonBuilder<global::typedstatebuilder.valueunset  , tfirstnamestate="">.SetLastNameCore<tfirstnamestate>(builder, value);

    /// <summary>
    /// Sets the firstName.
    /// </summary>
    /// <param name="builder">The builder.
    /// <param name="value">The value for <c>firstName</c>.
    /// <returns>
    /// The updated builder.
    /// </returns>
    public static TypedPersonBuilder<tlastnamestate  , global::typedstatebuilder.valueset=""> SetFirstName<tlastnamestate>(this TypedPersonBuilder<tlastnamestate  , global::typedstatebuilder.valueunset=""> builder, string value)
    where TLastNameState : global::TypedStateBuilder.IValueState
        =&gt; TypedPersonBuilder<tlastnamestate  , global::typedstatebuilder.valueunset="">.SetFirstNameCore<tlastnamestate>(builder, value);

    /// <summary>
    /// Builds the result.
    /// </summary>
    /// <param name="builder">The builder.
    /// <returns>
    /// The built result.
    /// </returns>
    /// <exception cref="System.AggregateException">Thrown if validation fails.</exception>
    public static global::Builder.Person Build(this TypedPersonBuilder<global::typedstatebuilder.valueset  , global::typedstatebuilder.valueset=""> builder)
        =&gt; TypedPersonBuilder<global::typedstatebuilder.valueset  , global::typedstatebuilder.valueset="">.BuildCore(builder);

}

}

namespace TypedStateBuilder
{

public static partial class TypedStateBuilders
{
    /// <summary>
    /// Creates a new <see cref="Builder.TypedPersonBuilder">.
    /// </see></summary>
    /// <returns>
    /// A new builder instance.
    /// </returns>
    public static global::Builder.TypedPersonBuilder<global::typedstatebuilder.valueunset  , global::typedstatebuilder.valueunset=""> CreatePersonBuilder()
    {
        var inner = global::Builder.PersonBuilder_Accessors.Create();
        return new global::Builder.TypedPersonBuilder<global::typedstatebuilder.valueunset  , global::typedstatebuilder.valueunset="">(inner);
    }

}
}

// <auto-generated>
#nullable enable
using System;

namespace TypedStateBuilder;

/// <summary>
/// Represents a builder step state.
/// </summary>
public interface IValueState { }

/// <summary>
/// Indicates that a builder step has been set.
/// </summary>
public sealed class ValueSet : IValueState { }

/// <summary>
/// Indicates that a builder step has not been set.
/// </summary>
public sealed class ValueUnset : IValueState { }

/// <summary>
/// Marks a method as a build method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class BuildAttribute : Attribute
{
    /// <summary>
    /// Marks a method as a build method.
    /// </summary>
    public BuildAttribute()
    {
    }

    /// <summary>
    /// Marks a method as a build method for a specific branch.
    /// </summary>
    /// <param name="targetBranch">The branch path.
    public BuildAttribute(string targetBranch)
    {
    }
}

/// <summary>
/// Marks a field as a builder step.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class StepForValueAttribute : Attribute
{
    /// <summary>
    /// Marks a required builder step.
    /// </summary>
    public StepForValueAttribute()
    {
    }

    /// <summary>
    /// Marks an optional builder step with a default value provider.
    /// </summary>
    /// <param name="providerMemberName">The provider member name.
    public StepForValueAttribute(string providerMemberName)
    {
    }
}

/// <summary>
/// Assigns a step to a branch.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class StepBranchAttribute : Attribute
{
    /// <summary>
    /// Assigns a step to a branch.
    /// </summary>
    /// <param name="branchPath">The branch path.
    public StepBranchAttribute(string branchPath)
    {
    }
}

/// <summary>
/// Adds another way to set a builder step.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public sealed class StepOverloadAttribute : Attribute
{
    /// <summary>
    /// Adds another way to set a builder step.
    /// </summary>
    /// <param name="overloadMemberName">The overload member name.
    public StepOverloadAttribute(string overloadMemberName)
    {
    }
}

/// <summary>
/// Adds validation for a builder step.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public sealed class ValidateValueAttribute : Attribute
{
    /// <summary>
    /// Adds validation for a builder step.
    /// </summary>
    /// <param name="validatorMemberName">The validator member name.
    public ValidateValueAttribute(string validatorMemberName)
    {
    }
}

/// <summary>
/// Enables typed builder generation for a builder class.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class TypedStateBuilderAttribute : Attribute
{
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator


Posted

in

,

by

Tags: