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
  • Nejnovější NuGet verze balíčku Kalicz.StrongTypes
  • Celkový počet stažení balíčku Kalicz.StrongTypes
  • Stav buildu na GitHub Actions
  • Licence projektu

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