On this page:
2.1 Basic TOML DSL
toml
toml-ref
2.2 Custom TOML DSLs
2.2.1 Basic usage (no validation)
2.2.2 With a schema
2.2.3 Using additional bindings in the schema
2.3 Low-level APIs
2.3.1 Validation
define-toml-schema
exn:  fail:  toml:  validation
2.3.1.1 Transformer predicates
readable-datum?
2.3.2 Syntax reader
make-toml-syntax-reader
get-info
9.0

2 Module Reference🔗

2.1 Basic TOML DSL🔗

 #lang toml/config package: toml-config-lib

A #lang for parsing TOML files into Racket modules. Files using #lang toml/config are parsed as TOML and provide the result as a hash table.

value

toml : hash?

The parsed TOML data, represented as an immutable hash table with symbol keys. Tables become nested hash tables, and all TOML data types are converted to their Racket equivalents.

procedure

(toml-ref data    
  path-component ...    
  [#:default default])  any/c
  data : hash?
  path-component : (or/c symbol? exact-nonnegative-integer?)
  default : any/c = (λ () (error ...))
Variadic convenience function for accessing nested TOML values using a path of keys and array indices.

Path components can be:
  • Symbols (possibly dotted): traverse into hash tables

  • Exact non-negative integers: index into lists

If any key in the path is missing or an array index is out of bounds, default is returned (or called if it’s a procedure).

; Dotted key notation
(toml-ref data 'database.host)
; equivalent to:
(hash-ref (hash-ref data 'database) 'host)
 
; Array indexing
(toml-ref data 'database.replicas 0 'host)
; equivalent to:
(hash-ref (first (hash-ref (hash-ref data 'database) 'replicas)) 'host)
 
; With defaults
(toml-ref data 'missing.key #:default "fallback")
; => "fallback"
(toml-ref data 'replicas 99 'host #:default "n/a")
; => "n/a"

2.2 Custom TOML DSLs🔗

 (require toml/config/custom) package: toml-config-lib

A module language for quickly creating custom TOML-based #langs with validation.

When you use #lang toml/config/custom as a module language (in a reader submodule), it provides a #%module-begin that handles all the reader plumbing for you. You just specify an optional schema, and it generates a read-syntax macro and provides the get-info function automatically.

The module language also provides common predicates and contracts that are useful in schemas: everything from racket/base, racket/contract/base, and non-empty-string?.

2.2.1 Basic usage (no validation)🔗

The simplest custom reader just parses TOML without any validation:

"myapp/config.rkt"

#lang racket/base
 
(module reader toml/config/custom)

Now TOML files can use #lang myapp/config (assuming this module is present in a package that uses the myapp collection) and they’ll get the same behavior as #lang toml/config.

2.2.2 With a schema🔗

To add validation that takes place at compile time, use the #:schema keyword inside the reader submodule, followed by field specifications:

"myapp/config.rkt"

#lang racket/base
 
(module reader toml/config/custom
 #:schema ([title string? required]
           [port (integer-in 1 65535) (optional 8080)]
           [database (table
                       [host string? required]
                       [port integer? required])]))

See define-toml-schema for info on the field-spec syntax.

By default, the schema expression inside a #lang toml/config/custom module has access to bindings from racket/base, racket/contract/base and to non-empty-string? from racket/string.

2.2.3 Using additional bindings in the schema🔗

You can add require and/or define statements in front of the #:schema keyword. This allows you to use custom predicates/contracts in your schema expression.

For example:

"myapp/example.rkt"

#lang racket/base
 
(module reader toml/config/custom
  (require gregor)
  (define (even-day? v) (and (date-provider? v) (even? (->day v))))
  #:schema [(birthdate even-day? required)])

You can also use module* with (require (submod "..")) to bring parent bindings into scope:

"myapp/example.rkt"

#lang racket/base
 
(define (valid-port? n)
 (and (integer? n) (>= n 1024) (<= n 65535)))
 
(provide valid-port?)
 
(module* reader toml/config/custom
 (require (submod ".."))
 #:schema ([title string? required]
           [port integer? valid-port? (optional 8080)]))

2.3 Low-level APIs🔗

Most people won’t need more than toml/config/custom to implement their own TOML DSLs. But if you want more control over the reader implementation, or if you’re not using toml/config/custom as a module language, you can use these lower-level functions directly.

Here’s an example using module+ to create a reader submodule that accesses custom predicates from the parent module:

"myapp/config.rkt"

#lang racket/base
 
(define (valid-title? s)
 (and (string? s)
      (> (string-length s) 0)
      (<= (string-length s) 50)))
 
(define (valid-port? n)
 (and (integer? n)
      (>= n 1024)
      (<= n 65535)))
 
(module+ reader
 (require toml/config/schema
          toml/config/reader)
 (provide read-syntax get-info)
 
 (define-toml-schema compiled-schema
   [title string? valid-title? required]
   [port integer? valid-port? (optional 8080)])
 
 (define read-syntax
   (make-toml-syntax-reader compiled-schema)))

Note that when using the low-level APIs, you need to provide both read-syntax and get-info yourself (whereas toml/config/custom does this automatically).

2.3.1 Validation🔗

 (require toml/config/schema) package: toml-config-lib

This module provides the framework for validating hash tables against a schema, with friendly error messages. A schema can make use of simple predicates or flat contracts, but no contracts are actually installed.

A validator is a function that takes a hash table (such as parsed TOML data) and either returns a value (typically a hash table, but not required) or raises an exception if validation fails. Validators created with define-toml-schema also apply default values to the data before returning it.

syntax

(define-toml-schema id field-spec ...)

 
field-spec = [key type-check ... req-or-opt]
  | [key (table field-spec ...) maybe-req-or-opt]
  | [key (array-of table field-spec ...) req-or-opt]
     
req-or-opt = required
  | optional
  | (optional default-expr)
     
maybe-req-or-opt = 
  | required
  | optional
Creates a validator function bound to id.

Each named key is followed by one or more type-check predicates (or flat contracts) that are applied to the value supplied for the key.

Ending a field-spec with required causes an exception to be thrown if the key is not present. Ending with optional allows the key to be absent; use (optional default-expr) to give the key a default value when not supplied. Note that the default value is not checked against the type-check expressions.

A table field-spec validates a nested table with its own field specs. It can be followed by required or optional; if neither is specified, required is assumed. When a table is optional and missing, its field validations are skipped.

An array-of table field-spec validates an array of tables (TOML’s [[name]] syntax). Each element in the array is validated against the nested field specs. Defaults are applied to each array element individually.

Type checks can be any predicate (e.g. string?, integer?) or flat contract (e.g. (integer-in 1 100), (listof string?), (or/c "ascending" "descending")).

The resulting validator checks that all required keys are present, validates types for all present keys, applies default values for missing optional keys and returns the (possibly modified) hash table.

Examples:
> (define-toml-schema my-schema
    [name string? required]
    [age (integer-in 0 150) required]
    [email string? optional]
    [admin boolean? (optional #f)]
    [settings (table
                [theme string? required]
                [notifications boolean? (optional #t)])])
> (define toml-1
    (string-append*
      (add-between
       '("name = \"Alice\""
         "age = 30"
         "[settings]")
        "\n")))
> (my-schema (parse-toml toml-1))

settings.theme: required key is missing

  → Add 'theme = <value>' to the configuration

> (define toml-2
    (string-append*
      (add-between
       '("name = \"Alice\""
         "age = 30"
         "[settings]"
         "theme = \"red\"")
        "\n")))
> (my-schema (parse-toml toml-2))

'#hasheq((admin . #f)

         (age . 30)

         (name . "Alice")

         (settings . #hasheq((notifications . #t) (theme . "red"))))

Arrays of tables are validated with array-of:

Examples:
> (define-toml-schema products-schema
    [products (array-of table
                [name string? required]
                [sku integer? required]
                [color string? (optional "black")])
              required])
> (define products-toml
    (string-append*
     (add-between
      '("[[products]]"
        "name = \"Hammer\""
        "sku = 738594937"
        "color = \"red\""
        ""
        "[[products]]"
        "name = \"Nail\""
        "sku = 284758393")
       "\n")))
> (products-schema (parse-toml products-toml))

'#hasheq((products

          .

          (#hasheq((color . "red") (name . "Hammer") (sku . 738594937))

           #hasheq((color . "black") (name . "Nail") (sku . 284758393)))))

Arrays of tables can be nested:

Examples:
> (define-toml-schema fruits-schema
    [fruits (array-of table
              [name string? required]
              [varieties (array-of table
                           [name string? required])
                         optional])
            required])
> (define fruits-toml
    (string-append*
     (add-between
      '("[[fruits]]"
        "name = \"apple\""
        ""
        "[[fruits.varieties]]"
        "name = \"red delicious\""
        ""
        "[[fruits.varieties]]"
        "name = \"granny smith\""
        ""
        "[[fruits]]"
        "name = \"banana\"")
       "\n")))
> (fruits-schema (parse-toml fruits-toml))

'#hasheq((fruits

          .

          (#hasheq((name . "apple")

                   (varieties

                    .

                    (#hasheq((name . "red delicious"))

                     #hasheq((name . "granny smith")))))

           #hasheq((name . "banana")))))

struct

(struct exn:fail:toml:validation (message
    continuation-marks
    key-path
    expected
    actual)
    #:extra-constructor-name make-exn:fail:toml:validation)
  message : string?
  continuation-marks : continuation-mark-set?
  key-path : (listof symbol?)
  expected : any/c
  actual : any/c
Exception raised when TOML validation fails.

The key-path field contains the path to the problematic key as a list of symbols (e.g., '(database host) for database.host).

The expected and actual fields contain the expected type/value and the actual value that failed validation.

2.3.1.1 Transformer predicates🔗

In addition to simple predicates that return #t or #f, schema type checks can be transformer predicates that transform the validated value. A transformer predicate returns:

  • #f if validation fails

  • #t if validation passes and the value should remain unchanged

  • Any other value (or a box containing the new value) to replace the original value

When multiple type checks are specified for a field, they are applied sequentially: each predicate receives the (possibly transformed) output of the previous one. This allows combining validation and transformation in a pipeline.

A validator defined with define-toml-schema will automatically unbox any boxed value returned by a predicate and pass the unboxed value to any later predicates. This provides a way for a transformer predicate to return #f as the intended result of the transformation without causing validation to fail.

When a predicate transformes a field value, the resulting hash table may no longer satisfy tomlexpr? since the transformed values may not be valid TOML data types.

procedure

(readable-datum? v)  (or/c #f box?)

  v : any/c
A transformer predicate that attempts to read a single datum from a string value using read. Returns a box containing the parsed datum on success, or #f if:

  • the value is not a string

  • the string cannot be parsed as a Racket datum

  • there is leftover content after the datum

Use readable-datum? to embed Racket data structures in TOML configuration files:

Examples:
> (define-toml-schema config-with-expr
    [filter-expr string? readable-datum? required]
    [name string? required])
> (define toml-expr
    (string-append*
      (add-between
       '("filter-expr = '(lambda (x) (> x 10))'"
         "name = 'threshold-filter'")
       "\n")))
> (define validated (config-with-expr (parse-toml toml-expr)))
> (toml-ref validated 'filter-expr)

'(lambda (x) (> x 10))

2.3.2 Syntax reader🔗

 (require toml/config/reader) package: toml-config-lib

procedure

(make-toml-syntax-reader validator)

  (-> any/c input-port? syntax?)
  validator : (-> hash? any/c)
Creates a read-syntax function for a TOML reader.

The validator is a validator function (typically created with define-toml-schema, but can be any function that takes a hash table). It’s called on the parsed TOML data before the module is created.

The returned function handles:
  • Reading the entire input port as a string

  • Parsing the TOML using parse-toml

  • Running the validator, converting any parse and validation errors to syntax errors

  • Generating a module that provides a toml binding

(define-toml-schema my-schema
  [title string? required])
 
(define read-syntax
  (make-toml-syntax-reader my-schema))

procedure

(get-info in mod-path line col pos)  (-> any/c any/c any/c)

  in : input-port?
  mod-path : module-path?
  line : (or/c #f exact-positive-integer?)
  col : (or/c #f exact-nonnegative-integer?)
  pos : (or/c #f exact-positive-integer?)
Returns a function that provides metadata about the TOML language to DrRacket and other tools. Currently supports 'color-lexer for TOML syntax highlighting in DrRacket.

This function is automatically provided by toml/config/custom, but if you’re implementing a reader using the low-level APIs with a manual reader submodule, you need to provide it yourself. This will ensure DrRacket applies sensible syntax coloring for your custom TOML #langs.

See Source-Handling Configuration for more information about get-info and reader extensions.