Config templates (experimental)
Requires the
templateand 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 |
|
|
| TOML | YAML |
|
|
Applying the desired casing for field names should be done with serde
rename_allon 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
formatoption 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,
}
}
Header and footer
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 |
|
|
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.