Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result

Derived Schemas

[<DeriveSchema>] removes the mechanical duplication between a wire record and its Schema<_> declaration. The build reads ordinary F# source, infers a schema from each marked record, and generates normal constructor-last Schema DSL code before F# compilation.

Fragment
namespace MyApp.Wire

open Reified.DerivedSchema

[<DeriveSchema>]
type Signup =
    { [<Email; Present>]
      Email: string
      [<AtLeast 18>]
      Age: int
      Tags: string list }

The generated companion module provides:

Fragment
Signup.schema    // Schema<Signup>
Signup.parse     // Data -> Result<Signup, SchemaErrors>
Signup.validate  // Signup -> Result<Signup, SchemaErrors>

This is source generation, not runtime reflection. The attributes are inert metadata; the generated F# uses the same Schema DSL as a hand-written declaration and therefore works with parsing, inspection, JSON codecs, JSON Schema, NativeAOT, trimming, and Fable.

Reified.SchemaGen matches [<DeriveSchema>] by its bare attribute name in the source syntax tree, before the project type-checks, not by resolving it to the specific Reified.DerivedSchema.DeriveSchemaAttribute type. An unrelated attribute of your own also named DeriveSchema — or DeriveSchemaAttribute, since the generator strips the Attribute suffix — in scope in the same file would match the same way. There is no resolution-based opt-out for DeriveSchema: keep the names DeriveSchema, DeriveUnion, and SchemaConstructor out of files that participate in generation, or expect the generator to treat your attribute as one of its own.

Guides

  • Set up build generation, package reference, generated-file modes, and MSBuild properties.
  • Attributes, every supported attribute and its Schema DSL equivalent.
  • How inference works, records, field types, names, constructors, unions, and diagnostics.
  • Generated code, exactly what the companion file contains, and where your own mapping code goes.

When to derive

Derivation is intended for public, permissive boundary records: a wire shape you do not own, or one with frozen versions that must keep decoding. Keep domain invariants in refined values or private domain types, then map the parsed wire record through a domain constructor; see Separate Wire and Domain Models. If you own the model end to end and it has no version history, write the Schema<'model> by hand instead and skip the DTO. See Schema or Generated DTO for the full decision.