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

Runnable Examples

This page shows the examples that are executed during the docs build, so the public docs stay tied to real code and observed output.

The examples below are built from the repository projects, run with the current source, and then written back into this page.

The code blocks keep the important API calls on the same lines as the values they bind, with trailing comments where that makes the signature easier to read. The examples prefer the normal direct-bind style inside computation expressions, so the docs reflect the recommended day-to-day usage.

Refined Catalog Example

This example shows a request boundary that parses strings, builds refined numeric/text/collection values, chooses a domain union case, and rejects invalid input before the domain record is created.

Run it:

REIFIED_EXAMPLE=refined-catalog dotnet run --project examples/Reified.Schema.Examples/Reified.Schema.Examples.fsproj --nologo

Source:

Source code:

module RefinedCatalogExample

open Reified

open System
open Reified.Result
open Reified.ResultDSL
open Reified.ConstraintDSL
open Reified.Refinements

// Slug is no longer a catalogue entry: it carries no invariant past the boundary, so it
// is defined here from the same constraints the built-in type used, exactly like Sku below.
type Slug = private Slug of string

type ProductId =
    private
    | ProductId of int

    member this.Value =
        let (ProductId value) = this
        value

module ProductId =
    let refinement = Refinement.define (Constraint.notEqualTo 0) ProductId _.Value
    let create value = Refinement.create refinement value
type ProductSlug = ProductSlug of Slug
type DisplayName = DisplayName of NonBlankString
type ProductTags = ProductTags of DistinctList<Slug>
type Quantity =
    private
    | Quantity of int

    member this.Value =
        let (Quantity value) = this
        value

module Quantity =
    let refinement = Refinement.define (Constraint.greaterThan 0) Quantity _.Value
    let create value = Refinement.create refinement value
type ContactEmail = private ContactEmail of string
type Sku = private Sku of string
type Rating = private Rating of int
type UnitPrice = private UnitPrice of decimal

module Slug =
    let value (Slug value) = value

    let create value : Result<Slug, Violation> =
        value
        |> Constraint.check (Constraint.all [ present; pattern "^[a-z0-9]+(-[a-z0-9]+)*$" ])
        |> Result.map (fun () -> Slug value)

module ContactEmail =
    let value (ContactEmail value) = value

    let create value : Result<ContactEmail, Violation> =
        value
        |> Constraint.check (Constraint.all [ present; email; maxLength 254 ])
        |> Result.map (fun () -> ContactEmail value)

module Sku =
    let value (Sku value) = value

    let create value : Result<Sku, Violation> =
        value
        |> Constraint.check (Constraint.all [ present; lengthBetween 3 12; pattern "^[A-Z0-9-]+$" ])
        |> Result.map (fun () -> Sku value)

module Rating =
    let value (Rating value) = value

    let create value : Result<Rating, Violation> =
        value |> Constraint.check (Constraint.between 1 5) |> Result.map (fun () -> Rating value)

module UnitPrice =
    let value (UnitPrice value) = value

    let create value : Result<UnitPrice, Violation> =
        value |> Constraint.check (greaterThan 0m) |> Result.map (fun () -> UnitPrice value)

type Discount =
    | Percent of Quantity
    | Code of Slug

type PublishWindow =
    { Range: Interval<DateTimeOffset> }

type ProductRequest =
    { Id: ProductId
      Slug: ProductSlug
      DisplayName: DisplayName
      Tags: ProductTags
      Quantity: Quantity
      ContactEmail: ContactEmail
      Sku: Sku
      Rating: Rating
      UnitPrice: UnitPrice
      Discount: Discount
      PublishWindow: PublishWindow }

let sequenceResults values =
    let folder next state =
        match next, state with
        | Ok value, Ok values -> Ok(value :: values)
        | Error error, _ -> Error error
        | _, Error error -> Error error

    values
    |> List.foldBack folder
    <| Ok []

let private parseError error = Error(sprintf "%A" error)
let private checkError failures = Error(Violation.render failures)

let parseDiscount (raw: string) : Result<Discount, string> =
    let parsePercent value =
        result {
            let! parsed = Parse.int value |> Result.mapError (sprintf "%A")
            let! percent = Quantity.create parsed |> Result.mapError Violation.render
            return Percent percent
        }
    match parsePercent raw with
    | Ok value -> Ok value
    | Error _ -> Slug.create raw |> Result.map Code |> Result.mapError Violation.render

let createProductRequest rawId rawSlug rawDisplayName rawTags rawQuantity rawContactEmail rawSku rawRating rawUnitPrice rawDiscount publishStart publishEnd : Result<ProductRequest, string> =
    result {
        let! parsedId = Parse.int rawId |> Result.mapError (sprintf "%A")
        let! id = ProductId.create parsedId |> Result.mapError Violation.render
        let! slug = Slug.create rawSlug |> Result.mapError Violation.render
        let! displayName = Refine.nonBlankString rawDisplayName |> Result.mapError Violation.render
        let! tags = rawTags |> List.map Slug.create |> sequenceResults |> Result.mapError Violation.render
        let! distinctTags = Refine.distinctList tags |> Result.mapError Violation.render
        let! parsedQuantity = Parse.int rawQuantity |> Result.mapError (sprintf "%A")
        let! quantity = Quantity.create parsedQuantity |> Result.mapError Violation.render
        let! contactEmail = ContactEmail.create rawContactEmail |> Result.mapError Violation.render
        let! sku = Sku.create rawSku |> Result.mapError Violation.render
        let! parsedRating = Parse.int rawRating |> Result.mapError (sprintf "%A")
        let! rating = Rating.create parsedRating |> Result.mapError Violation.render
        let! parsedUnitPrice = Parse.decimal rawUnitPrice |> Result.mapError (sprintf "%A")
        let! unitPrice = UnitPrice.create parsedUnitPrice |> Result.mapError Violation.render
        let! discount = parseDiscount rawDiscount
        let! range = Interval.create publishStart publishEnd |> Result.mapError Violation.render
        return { Id = id; Slug = ProductSlug slug; DisplayName = DisplayName displayName; Tags = ProductTags distinctTags; Quantity = quantity; ContactEmail = contactEmail; Sku = sku; Rating = rating; UnitPrice = unitPrice; Discount = discount; PublishWindow = { Range = range } }
    }


let run () =
    createProductRequest "1" "product" "Product" [ "featured" ] "2" "ada@example.com" "SKU-1" "5" "12.50" "10" DateTimeOffset.UtcNow DateTimeOffset.UtcNow
    |> printfn "Refined catalog: %A"

Refined Value Schema Example

This example shows total domain conversions built with Schema.convert, composed into a record schema, and lowered to executable checks.

Run it:

REIFIED_EXAMPLE=refined-value-schema dotnet run --project examples/Reified.Schema.Examples/Reified.Schema.Examples.fsproj --nologo

Source:

Source code:

module RefinedValueSchemaExample

open Reified
open Reified.SchemaDSL

/// <summary>An email address refined over Reified's text primitive, carrying the well-known email format.</summary>
type Email =
    private
    | Email of string

    static member Schema(_: Email) : Schema<Email> =
        Schema.text
        |> Schema.constrainAll [ Constraint.present; Constraint.email ]
        |> Schema.convert Email (fun (Email value) -> value)
        |> Schema.withFormat SchemaFormat.email

module Email =
    let create (value: string) = Email value
    let value (Email value) = value

    let schema : Schema<Email> = SchemaDefaults.Resolve()

/// <summary>A bounded-text domain value whose length constraints live on the raw text schema.</summary>
type ContactName =
    private
    | ContactName of string

    static member Schema(_: ContactName) : Schema<ContactName> =
        Schema.text
        |> Schema.constrainAll [ Constraint.minLength 2; Constraint.maxLength 40 ]
        |> Schema.convert ContactName (fun (ContactName value) -> value)

module ContactName =
    let create (value: string) = ContactName value
    let value (ContactName value) = value

    let schema : Schema<ContactName> = SchemaDefaults.Resolve()

/// <summary>A quantity that must always be positive (strictly greater than zero).</summary>
type Quantity =
    private
    | Quantity of int

    static member Schema(_: Quantity) : Schema<Quantity> =
        Schema.int
        |> Schema.constrain (Constraint.greaterThan 0)
        |> Schema.convert Quantity (fun (Quantity value) -> value)

module Quantity =
    let create (value: int) = Quantity value
    let value (Quantity value) = value

    let schema : Schema<Quantity> = SchemaDefaults.Resolve()

/// <summary>A running total that must never go negative, but zero is allowed.</summary>
type Balance =
    private
    | Balance of decimal

    static member Schema(_: Balance) : Schema<Balance> =
        Schema.decimal
        |> Schema.constrain (Constraint.atLeast 0m)
        |> Schema.convert Balance (fun (Balance value) -> value)

module Balance =
    let create (value: decimal) = Balance value
    let value (Balance value) = value

    let schema : Schema<Balance> = SchemaDefaults.Resolve()

type Contact =
    { Email: Email
      Name: ContactName
      Quantity: Quantity
      Balance: Balance }

let contactSchema =
    schema<Contact> {
        field _.Email
        field _.Name
        field _.Quantity
        field _.Balance
        construct (fun email name quantity balance ->
            { Email = email
              Name = name
              Quantity = quantity
              Balance = balance })
    }

let run () =
    let contact =
        { Email = Email.create "ada@example.com"
          Name = ContactName.create "Ada"
          Quantity = Quantity.create 3
          Balance = Balance.create 0m }

    let emailCheck = Email.schema |> SchemaCheck.text
    let nameCheck = ContactName.schema |> SchemaCheck.text
    let quantityCheck = Quantity.schema |> SchemaCheck.ordered<int, _>
    let balanceCheck = Balance.schema |> SchemaCheck.ordered<decimal, _>

    printfn "Email check: %A" (emailCheck contact.Email)
    printfn "Name check: %A" (nameCheck contact.Name)
    printfn "Quantity check: %A" (quantityCheck contact.Quantity)
    printfn "Balance check: %A" (balanceCheck contact.Balance)

    printfn "Invalid email check: %A" (emailCheck (Email.create ""))
    printfn "Invalid name check: %A" (nameCheck (ContactName.create "A"))
    printfn "Invalid quantity check: %A" (quantityCheck (Quantity.create 0))
    printfn "Invalid balance check: %A" (balanceCheck (Balance.create -1m))