Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Config templates (experimental)

Requires the template and desired format Cargo feature.

With our format renderers, you can generate a config template in a specific format. This template will include all fields, default values, comments, metadata, and is useful for situations like scaffolding files during installation.

To utilize, instantiate a generator, add types to render, and generate the output file.

#![allow(unused)]
fn main() {
use schematic::schema::SchemaGenerator;

let mut generator = SchemaGenerator::default();
generator.add::<CustomType>();
generator.generate(output_dir.join("config.json"), renderer)?;
}

Supported formats

JSON

The JsonTemplateRenderer will render JSON templates without comments. Any commented related options will be force disabled.

#![allow(unused)]
fn main() {
use schematic::schema::{JsonTemplateRenderer, TemplateOptions};

JsonTemplateRenderer::default();
JsonTemplateRenderer::new(TemplateOptions::default());
}

JSONC

The JsoncTemplateRenderer will render JSON templates with comments. We suggest using the .jsonc file extension, but not required.

#![allow(unused)]
fn main() {
use schematic::schema::{JsoncTemplateRenderer, TemplateOptions};

JsoncTemplateRenderer::default();
JsoncTemplateRenderer::new(TemplateOptions::default());
}

Pkl

The PklTemplateRenderer will render Pkl templates.

#![allow(unused)]
fn main() {
use schematic::schema::{PklTemplateRenderer, TemplateOptions};

PklTemplateRenderer::default();
PklTemplateRenderer::new(TemplateOptions::default());
}

A template can amend the Pkl modules of its types, so that Pkl type checks it, by adding an amends clause to the header option. The modules then type every property, so lists and maps amend the values they’re declared with, as in tags { "web" }, which gives the objects within them their declared type. Without one, lists and maps are created with new Listing {} and new Mapping {}.

#![allow(unused)]
fn main() {
PklTemplateRenderer::new(TemplateOptions {
	header: "amends \"pkl/ServerConfig.pkl\"\n\n".into(),
	..TemplateOptions::default()
});
}

A module with required fields expects the template to set them, so render the modules with mark_struct_fields_required disabled when fields are hidden or commented out.

TOML

The TomlTemplateRenderer will render TOML templates.

#![allow(unused)]
fn main() {
use schematic::schema::{TomlTemplateRenderer, TemplateOptions};

TomlTemplateRenderer::default();
TomlTemplateRenderer::new(TemplateOptions::default());
}

YAML

The YamlTemplateRenderer will render YAML templates.

#![allow(unused)]
fn main() {
use schematic::schema::{YamlTemplateRenderer, TemplateOptions};

YamlTemplateRenderer::default();
YamlTemplateRenderer::new(TemplateOptions::default());
}

Root document

A template represents a single document, typically for a struct. In Schematic, the last type to be added to SchemaGenerator will be the root document, while all other types will be ignored. For example:

#![allow(unused)]
fn main() {
// These are only used for type information
generator.add::<FirstConfig>();
generator.add::<SecondConfig>();
generator.add::<ThirdConfig>();

// This is the root document
generator.add::<LastType>();
generator.generate(output_dir.join("config.json"), renderer)?;
}

Caveats

By default arrays and objects do not support default values, and will render [] and {} respectively. This can be customized with the expand_fields option.

Furthermore, enums and unions only support default values when explicitly marked as such. For example, with #[default].

And lastly, when we’re unsure of what to render for a value, we’ll render null. This isn’t a valid value for TOML, and may not be what you expect.

Example output

Given the following type:

#![allow(unused)]
fn main() {
#[derive(Config)]
struct ServerConfig {
	/// The base URL to serve from.
	#[setting(default = "/")]
	pub base_url: String,

	/// The default port to listen on.
	#[setting(default = 8080, env = "PORT")]
	pub port: usize,
}
}

Would render the following formats:

JSONC Pkl
{
	// The base URL to serve from.
	"base_url": "/",

	// The default port to listen on.
	// @env PORT
	"port": 8080
}
// The base URL to serve from.
base_url = "/"

// The default port to listen on.
// @env PORT
port = 8080

TOML YAML
# The base URL to serve from.
base_url = "/"

# The default port to listen on.
# @env PORT
port = 8080
# The base URL to serve from.
base_url: "/"

# The default port to listen on.
# @env PORT
port: 8080

Applying the desired casing for field names should be done with serde rename_all on the container.

Options

Custom options can be passed to the renderer using TemplateOptions.

#![allow(unused)]
fn main() {
use schematic::schema::TemplateOptions;

JsoncTemplateRenderer::new(TemplateOptions {
	// ...
	..TemplateOptions::default()
});
}

The format option is required!

Indentation

The indentation of the generated template can be customized using the indent_char option. By default this is 2 spaces ( ).

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	indent_char: "\t".into(),
}
}

The spacing between fields can also be toggled with the newline_between_fields option. By default this is enabled, which adds a newline between each field.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	newline_between_fields: false,
}
}

Comments

All Rust doc comments (///) are rendered as comments above each field in the template. This can be disabled with the comments option.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	comments: false,
}
}

The header and footer options can be customized to add additional content to the top and bottom of the rendered template respectively.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	header: "$schema: \"https://example.com/schema.json\"\n\n".into(),
	footer: "\n\n# Learn more: https://example.com".into(),
}
}

Field display

By default all non-skipped fields in the root document (struct) are rendered in the template. If you’d like to hide certain fields from being rendered, you can use the hide_fields option. This option accepts a list of field names and also supports dot-notation for nested fields.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	hide_fields: vec!["key".into(), "nested.key".into()],
}
}

Alternatively, you can choose to only render specific fields with only_fields.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	only_fields: vec!["key".into(), "nested.key".into()],
}
}

And lastly, if you’d like to render a field but have it commented out by default, use the comment_fields option instead. This also supports dot-notation for nested fields.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	comment_fields: vec!["key".into(), "nested.key".into()],
}
}

Field names use the serde cased name, not the Rust struct field name.

Field expansion

For arrays and objects, we render an empty value ([] or {}) by default because there’s no actual data associated with the schema. However, if you’d like to render a single example item for a field, you can use the expand_fields option.

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	expand_fields: vec!["key".into(), "nested.key".into()],
}
}

Here’s an example of how this works:

Not expanded Expanded
{
	"proxies": []
}
{
	"proxies": [
		// An example proxy configuration.
		{
			"host": "",
			"port": 8080
		}
	]
}

Custom values

If you need more control of what gets rendered for a specific field, you can use the custom_values option. This option accepts a map of field names to custom values (schemas), and will take priority over default and empty values. Dot-notation for nested fields is also supported.

Using the example output above, we can override the port field to render 3000 instead of 8080 like so:

#![allow(unused)]
fn main() {
TemplateOptions {
	// ...
	custom_values: HashMap::from_iter([
		("port".into(), Schema::integer(IntegerType::new_unsigned(IntegerKind::Usize, 3000)))
	]),
}
}

If the custom schema type does not match the field type, it will trigger a panic.