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.
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.Fieldsis generated into your class. It takes exactly the number of fields you passed, soIntois checked at compile time rather than at run time.Schema.OptionalyieldsOption<int>, notint?. 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
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?