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

Field sets

Turn several fields into one object, gate a parse without producing a value, and write a rule that spans two fields.

A field set is how several schemas become one object. You list the fields; the generator writes the code that assembles them.

Derive from SchemaConfig<TIn, TOut> and mark the class partial. There is no attribute. Three rules apply, and each one is the reason a first attempt does not compile:

  • The class, and every type containing it, has to be partialWMSC0001.

  • It needs a constructor you can call with no arguments — WMSC0002.

  • Do not declare a member called Instance, Schema or FieldSetWMSC0003.

The four kinds of field

Required

Field<string> name =
    Schema.Required(subject.Name, Schema.Text.Trim().NotEmpty());

Absent, null, or failing the schema is a violation at that field's path. The value reaches your constructor as a plain string, never a string?.

An optional third argument overrides the message for the value being absent. The schema's own rules keep the messages they came with.

Field<string> title =
    Schema.Required(subject.Title, Guild.Title, "Every party needs {Path}.");

Optional

Field<Option<int>> size =
    Schema.Optional(subject.Size, Schema.Number.Int32.AtLeast(1).AtMost(6));

Absent is accepted. The value arrives as Option<int>, so null never reaches a rule and never reaches the constructed object.

A value that is present still has to pass the schema. Optional means "may be absent", not "may be wrong".

Forbidden

Reject a value at a path where you allow none.

This is the first of the three fields that yield Checked. Checked means "this rule passed, and it has nothing to hand you" — the field gates the parse without contributing to the object. Fields like that go to Refine and take no slot in the Into lambda.

Extend

Runs a schema over the whole subject. Another Checked field, so it goes to Refine too.

Checked

The other two Checked fields are built that way from the start. This one is made from a field that does parse a value, when you want the rules but not the value.

Reach for it when the caller has to send a field correctly but your type has no place for it — part of a wire contract another system reads, or an address you check and never store.

The value goes. Everything else stays: the rules still run, and a failure is still reported at that field's own path, so a caller is told which field was wrong.

Hand the result to Refine. Both kinds of field go there the same way — Required when the caller has to send it, Optional when they may.

Schema.Forbidden is not the same thing. It says the field must be absent, and these fields are allowed. Schema.Extend is not either — it reports at the subject's path rather than the field's, so a caller reading the violations by field name finds nothing under the name they sent.

A rule that spans two fields

Schema.For<T>() over the subject is the right home for a rule about more than one field. Its violations land at the subject's own path rather than under a field name, which is honest — the failure belongs to neither field alone.

Hand it to the field set with Schema.Extend.

Putting it together

Refine takes the fields that gate the parse without producing a value, so the Into lambda keeps one parameter per field in Schema.Fields — in order, and with no discards.

Pass only value-free fields to Refine. It takes the non-generic Field base, which drops the value side, so it will accept a field that parses something and then throw that something away. WMSC0005 warns when you do.

When you mean it, say so with AsChecked rather than listing the field in Schema.Fields and discarding it with a _ in the lambda. Those discards are positional: add, remove or reorder a field and the parameters you did want quietly bind to different fields whenever the types line up. AsChecked also keeps WMSC0005 working on the fields you did not mean to discard, which turning the warning off would not.

A nested schema is just a schema. Its violations arrive under the field's name, so a reader is told which one failed.

Gating without building

Some schemas exist only to say yes. Finish those with Checked() instead of Into.

There is nothing to construct, so there is no lambda and nothing to name.

What the generator writes

A shared Instance, and the Fields, Refine, Into and Checked members you call.

Fields is written at exactly your field count. That is why a wrong-sized Into lambda is a compile error rather than a surprise in production — WMSC0004.

Paths come from your source

A field's path is read from the expression you passed, using CallerArgumentExpression. subject.Title gives title, which is the case the whole design is built around.

Anything else keeps its punctuation. A method call, an indexer or a null-forgiving operator gives a path that reaches your logs and your API responses looking like source code. WMSC0008 warns when that happens; add .Named("...") to fix it.

Last updated

Was this helpful?