Přeskočit na obsah

Pár malých typů, které dělají C# bezpečnějším.

Kalicz.StrongTypes je C# NuGet balíček, který přidává bezkódové validace vynucené překladačem. Neplatná data se nikdy nedostanou do vašeho aplikačního kódu.

Instalace
dotnet add package Kalicz.StrongTypes

Proč se obtěžovat se silnými typy?

Přestaňte validovat vstup všude. Validujte jednou, na hranici, a zbytek nechte vynutit překladač.

Diagram ukazující, jak StrongTypes přesouvá validaci na hranici aplikace a zbytek kódu zůstává bez ochranných podmínek

Invarianty v typu

Pokud metoda přijímá NonEmptyString, prázdný řetězec se k ní nedostane. Práci odvede překladač — ne strážní podmínka v runtime.

Validace na hranici

Vestavěné konvertory pro System.Text.Json odmítnou neplatná data při deserializaci, ještě než se zavolá handler endpointu.

Výstižný záměr

Signatura jako Result<Order, OrderError> Place(Positive<int> qty) čtenáři přesně říká, co je dovoleno a co může selhat.

Malé a zaměřené

Žádný těžký framework, žádné runtime kouzlení s reflexí. Přidá se vedle existujícího kódu — typy se kombinují s tím, co už máte.

Co je uvnitř

Validované obaly

  • NonEmptyString

    Zaručeně nenulový, neprázdný řetězec, který není ani jen samé bílé znaky.

  • Positive<T>, NonNegative<T>, Negative<T>, NonPositive<T>

    Obaly vynucující znaménko nad libovolným INumber<T>.

  • NonEmptyEnumerable<T>

    Read-only sekvence s alespoň jedním prvkem — Head a Tail jsou vždy bezpečně přístupné.

Algebraické typy

  • Maybe<T>

    Volitelné hodnoty s Map a FlatMap. Hodí se pro HTTP PATCH (neměnit / vyprázdnit / nastavit).

  • Result<T, TError>

    Explicitní zacházení s chybami bez výjimek. Včetně podpory async.

Vestavěné integrace

  • System.Text.Json

    Automatické konvertory — bez nutnosti registrace.

  • EF Core value convertery

    Skrze doplňkový balíček Kalicz.StrongTypes.EfCore.

  • Generátory pro FsCheck

    Skrze doplňkový balíček Kalicz.StrongTypes.FsCheck pro property-based testing.

Diagram tří NuGet balíčků: hlavní Kalicz.StrongTypes plus volitelné doplňky EfCore a FsCheck

Jak to vypadá

Pár ukázek pro představu. Plnou dokumentaci a další příklady najdete na GitHubu.

Validace na hranici API

JSON konvertor odmítne neplatný vstup při deserializaci. Váš endpoint nikdy neuvidí špatnou hodnotu.

public record CreateUserRequest(
    NonEmptyString Name,
    Positive<int> Age);

[HttpPost]
public async Task<IResult> Create(CreateUserRequest req)
{
    // Žádná další validace tu není potřeba:
    // ASP pipeline odmítne neplatný vstup s 400.
    _users.Add(req.Name, req.Age);
    await _users.SaveChangesAsync();
    return Results.NoContent();
}

Explicitní vytváření hodnot

Každý typ má Create (vyhodí výjimku při neplatném vstupu) a TryCreate (vrátí null). Stringové typy mají i plynulou formu rozšíření.

// Stringy — tři varianty volání
// throws při neplatném vstupu
NonEmptyString name = NonEmptyString.Create(input);
// null při neplatném
NonEmptyString? safe = NonEmptyString.TryCreate(input);
// fluent rozšíření
NonEmptyString? fluent = input.AsNonEmpty();

// Čísla — vynucené znaménko nad INumber<T>
Positive<int> qty = Positive<int>.Create(5);
NonNegative<decimal>? price = amount.AsNonNegative();

// Sekvence — alespoň jeden prvek, Head bezpečné
NonEmptyEnumerable<string> tags = ["red", "blue"];
NonEmptyEnumerable<string>? maybe = list.AsNonEmpty();
string first = tags.Head;
Diagram porovnávající TryCreate (vrátí null při neplatném vstupu) a Create (vyhodí výjimku při neplatném vstupu)

Explicitní chyby s Result<T, TError>

Pattern matching na Success nebo Error — žádné výjimky, žádné out parametry.

Result<int, string> Parse(string s) =>
    int.TryParse(s, out var n) ? n : "not a number";

var result = Parse(input);
if (result.Success is { } value)
    Console.WriteLine($"got {value}");
if (result.Error is { } msg)
    Console.WriteLine($"failed: {msg}");
Diagram pipeline ukazující, jak hodnota Result<T, TError> prochází fázemi Map, MapError a FlatMap

Tři stavy v PATCH s Maybe<T>

Rozlišení "neměnit", "nastavit na null" a "nastavit na hodnotu" — letitý problém v REST API.

public record PatchUser(Maybe<string>? Nickname);

// null         → pole neměnit
// Some(null)   → vyprázdnit
// Some("abc")  → nastavit
if (request.Nickname is { } change)
    user.Nickname = change.Value;
Diagram tří stavů v PATCH: null (neměnit), Some(null) (vyprázdnit), Some(value) (nastavit)

Skládání s Maybe<T>

Řetězení volitelných operací bez vnořených null kontrol. Implicitní operátory zařídí zabalení — vrátíte T nebo Maybe.None a funguje to.

// Producent: implicitní převod z T nebo Maybe.None
Maybe<int> Parse(string s) =>
    int.TryParse(s, out var n) ? n : Maybe.None;

// Map: T -> U, automaticky zabalí v Maybe
Maybe<int> doubled = Parse("21").Map(x => x * 2);
// Some(42)

// FlatMap: T -> Maybe<U>, bez dvojitého zabalení
Maybe<int> sum = Parse("1").FlatMap(a =>
    Parse("2").Map(b => a + b));
// Some(3)
Diagram pipeline ukazující, jak Some a None hodnoty procházejí operacemi Map a FlatMap nad Maybe<T>

Začínáme

Zdrojový kód, balíčky a dokumentace.

Přihlásit se

Nemáte účet?

nebo