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());
}

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.
	// @envvar PORT
	"port": 8080
}
# The base URL to serve from.
base_url = "/"

# The default port to listen on.
# @envvar PORT
port = 8080

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

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

# The default port to listen on.
# @envvar 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()],
}
}

Additionally, 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
		}
	]
}