Getting Started

Tuxedo is a source generator, so it is referenced as a build-time only dependency.

<ItemGroup>
    <PackageReference Include="Tuxedo.SourceGenerator" Version="x.x.x">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
</ItemGroup>

A refined type

A refined type is a partial struct with a predicate attached to it. The generator reads the predicate and writes the type.

Say a password has to be at least eight characters long, and contain an uppercase letter, a number and a special character.


/// <summary>
/// A valid password string
/// </summary>
public readonly partial struct PasswordString
{
    // this is the definition of what makes a password string valid.
    // the name of the method combined with the type it operates on make up the name of the refined type
    // in this case PasswordString
    [Tuxedo.Refinement(
        "The string must be at least 8 characters long, contain at most 1 uppercase letter, number and special character. You provided '{value}'.",
        Name = nameof(PasswordString)
    )]
    private static bool IsValidPassword(string value)
    {
        if (value.Length < 8)
        {
            return false;
        }

        var hasUppercaseLetter = false;
        var hasNumber = false;
        var hasSpecialCharacter = false;
        foreach (var @char in value)
        {
            hasUppercaseLetter |= char.IsUpper(@char);
            hasNumber |= char.IsDigit(@char);
            hasSpecialCharacter |= !char.IsLetterOrDigit(@char) && !char.IsWhiteSpace(@char);
        }

        return hasUppercaseLetter && hasNumber && hasSpecialCharacter;
    }
}

The generated PasswordString carries the rule, and the way to use it is the same whatever the rule is,


bool isValid = PasswordString.TryParse("12Da3%sd", out var password, out _);
Assert.True(isValid);
Assert.Equal("12Da3%sd", password.Value);

TryParse returns false and fills the failure message when the value does not satisfy the predicate. Parse throws ArgumentOutOfRangeException with that same message instead.

Where the rules come from

The generator only turns predicates marked with Tuxedo.Refinement into types. Everything else in the partial struct is left alone.

See also

  • Refinement Rules - what the generator requires of a predicate
  • Examples - refined types for strings, numbers, dates and collections