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