Asynchrony
Rules that have to ask something — a database, a service — and the one place they may not go.
Most rules answer from the value alone. Some have to ask something else: is this title already taken, does this account exist. CheckAsync is for those.
CheckAsync
// The cheap rules first, then the round trip.
public static Schema<string, string> UniqueTitle(IQuestBoard board) =>
Schema.Text.Trim()
.NotEmpty()
.MaxLength(80)
.CheckAsync(
board.TitleIsFree,
ViolationCode.Duplicate,
"{Path} is already on the board, got {Received}.");The rule is handed the value and the parse's own cancellation token. It runs only when everything before it accepted, so it never sees a value the chain could not produce — your round trip is not spent on input that was already going to fail.
Otherwise it is Check. Same three arguments, same behaviour on failure: the value survives and the rest of the chain still runs.
ParseAsync
Result<string, SchemaViolation> result =
await UniqueTitle(board).ParseAsync(title, cancellationToken);ParseAsync returns a ValueTask<Result<TOut, SchemaViolation>>. Pass a cancellation token; the rule you wrote is given the same one.
A schema with no asynchronous rules can still be parsed with ParseAsync. It just completes without ever yielding.
An async rule cannot live in a field set
This is the rule to remember on this page.
SchemaConfig.Configure returns a value rather than a task, so a field set only ever runs the synchronous path — even when the caller used ParseAsync. An asynchronous rule reached from there throws InvalidOperationException. Nothing in the type system says so, because CheckAsync returns the same schema type a synchronous rule does.
WMSC0006 reports it at build time, which is where you would rather find out.
Compose it around the outside instead
The field set stays synchronous. The round trip happens once, after every cheap rule has already had its say — so a payload with three malformed fields never reaches your database at all.
Last updated
Was this helpful?