Use S7schema in your package

Introduction

If you maintain a package that requires a specific configuration format, S7schema lets you define a validated config class with minimal code. By extending S7schema, your package gets schema validation, YAML reading/writing, and schema documentation for free.

Define a schema

A JSON schema describes the structure and constraints of your configuration. In a real package, the schema should live in inst/ (e.g. inst/schema/my_schema.json) so it ships with the package and can be found via system.file().

For this vignette we use the example schema bundled with S7schema:

schema_path <- system.file("examples/schema.json", package = "S7schema")
{
  "$schema": "http://json-schema.org/draft-07/schema",
  "title": "My config schema",
  "description": "Simple example schema with one allowed entry",
  "type": "object",
  "properties": {
    "my_config_var": {
      "description": "My only configuration variable",
      "type": "number"
    }
  },
  "additionalProperties": false
}

Create a child class

The core pattern is to create an S7 class that inherits from S7schema and hard-codes the schema path in its constructor:

my_config_class <- S7::new_class(
  name = "my_config_class",
  parent = S7schema::S7schema,
  constructor = function(file) {
    S7::new_object(
      .parent = S7schema::S7schema(
        file = file,
        schema = system.file("examples/schema.json", package = "S7schema")
      )
    )
  }
)

Users of your package only need to supply the config file path — the schema is handled internally.

Use the child class

Construction

Creating an instance loads and validates the YAML file automatically:

config_path <- system.file("examples/config.yml", package = "S7schema")
x <- my_config_class(file = config_path)
print(x)
#> <my_config_class> List of 1
#>  $ my_config_var: int 1
#>  @ schema   : chr "/private/var/folders/fx/71by3f551qzb5wkxt82cv15m0000gp/T/RtmpapwPJP/Rinstdccf75252863/S7schema/examples/schema.json"
#>  @ validator: <S7schema::validator>
#>  .. @ context:Classes 'V8', 'environment' <environment: 0x106f16710> 
#>  @ file     : chr "/private/var/folders/fx/71by3f551qzb5wkxt82cv15m0000gp/T/RtmpapwPJP/Rinstdccf75252863/S7schema/examples/config.yml"

Accessing values

Since S7schema objects are lists, values are accessed directly:

x$my_config_var
#> [1] 1

Class hierarchy

The child class inherits from S7schema:

class(x)
#> [1] "my_config_class"    "S7schema::S7schema" "list"              
#> [4] "S7_object"

Validation

S7::validate() works on the child class just like on the parent:

x$my_config_var <- "not a number"
S7::validate(x)
#> Error in `validate_S7schema()`:
#> ! /my_config_var must be number
#> ✖ type: number

Method dispatch

Methods defined for S7schema work on child classes without extra code.

write_config() validates and writes to YAML:

tmp <- tempfile(fileext = ".yml")
x$my_config_var <- 42
write_config(x, file = tmp)
readLines(tmp)
#> [1] "my_config_var: 42.0"

document_schema() generates markdown documentation from the schema:

md <- document_schema(schema_path)
cat(md)
#> # My config schema
#> 
#> Simple example schema with one allowed entry
#> 
#> |Type   |Additional Properties |
#> |:------|:---------------------|
#> |object |No                    |
#> 
#> ## Properties
#> 
#> |Name          |Description                    |Type   |Required |
#> |:-------------|:------------------------------|:------|:--------|
#> |my_config_var |My only configuration variable |number |No       |

Note that if printed directly, the return of document_schema() is displayed as-is.

The header_start_level parameter controls the depth of the generated headings:

md <- document_schema(schema_path, header_start_level = 3)
cat(md)
#> ### My config schema
#> 
#> Simple example schema with one allowed entry
#> 
#> |Type   |Additional Properties |
#> |:------|:---------------------|
#> |object |No                    |
#> 
#> #### Properties
#> 
#> |Name          |Description                    |Type   |Required |
#> |:-------------|:------------------------------|:------|:--------|
#> |my_config_var |My only configuration variable |number |No       |