For the complete documentation index, see llms.txt. This page is also available as Markdown.

Schemas

Parse untrusted input into a type you could not have built without passing, and get every failure back at once.

Waystone.Monads.Schemas — a parser that hands back a Result.

New to this? Start with the Schemas guide. It builds one schema end to end and explains why you would want to. This page is the reference.

What it adds

A schema takes one type in and gives another type out. The type it gives out is one your caller could not have constructed by hand, so holding it is the proof that the input passed.

Reach for it at the edge — a request body, a message off a queue, a row from a file. Skip it inside your domain, where the types already say what is true.

Comparing it against FluentValidation? That one checks the object you built. This one builds it.

Write the checks once

A schema is a value. Declare it, name it, and reuse it.

public static class Guild
{
    public static readonly Schema<string, string> Title =
        Schema.Text.Trim().LengthBetween(3, 80);

    public static readonly Schema<string, string> Email =
        Schema.Text.Trim().Email();

    public static readonly Schema<decimal, decimal> Reward =
        Schema.Number.Decimal.Between(1m, 10_000m);
}

Put them together

Two types are involved. The input is whatever arrived — every field nullable, nothing checked. The output is the type you actually wanted.

Quest has no public constructor, so the schema is the only way to get one. That is the part doing the work.

QuestDto carries two fields this schema ignores, which is what a real payload looks like — you parse what you need and leave the rest.

Now derive from SchemaConfig<TIn, TOut>, mark the class partial, and list the fields. The generator writes the rest.

Two things in that snippet are worth a second look.

  • Schema.Fields is generated into your class. It takes exactly the number of fields you passed, so Into is checked at compile time rather than at run time.

  • Schema.Optional yields Option<int>, not int?. A missing value never reaches a rule and never reaches the constructor.

Parse something

The generator also writes a shared Instance, so there is nothing to new up.

Read the failures

An Err carries a SchemaViolation. It holds every individual Violation, each with the path it was found at and a message written for a human.

ToDictionary gives the shape most APIs return — one entry per path, holding that path's messages.

One parse reports everything

A schema does not stop at the first problem. Three bad fields give three violations, so your caller fixes their payload once instead of three times.

There is one exception, and it is deliberate. A failed Transform produces no value, so the rules after it on that chain cannot run. Its siblings are unaffected and still report.

Install it

The generator ships inside that package. There is nothing else to install and nothing to wire up.

The generator's own diagnostics use the WMSC prefix and are listed on Generator diagnostics.

Where to go next

Page
Covers

Schema.Text, Schema.Number, dates, enums, and the rules on each

Check, Transform, Not, When, All, Any, messages and codes

Lists, dictionaries, and the paths a violation carries

Required, Optional, Forbidden, Extend, Refine

CheckAsync, ParseAsync, and where an async rule may not go

Last updated

Was this helpful?