blob: 21aa5dfe86169d3b2054781b4f2269ee8bc36e64 [file] [log] [blame] [view]
# Best Practices for `config_schema`
## Defaults
The most common use-case should require the least amount of typing. This requires our configuration to have reasonable default values.
Suppose that you have a subsystem that groups a set of functionality together, and you want to nest it under the top-level `PlatformConfig`.
```
struct PlatformConfig {
...
#[serde(default)]
my_subsystem: MySubsystem,
}
```
Provide a `Default` implementation for `MySubsystem` if possible and use `#[serde(default)]` in the `PlatformConfig` to populate the struct if the entire subsystem is omitted.
Prefer `#[derive(Default)]` because it is simpler, and use the container-level attribute `#[serde(default)]` to allow the user to supply only the fields they care about, and let serde fill in the rest.
```
#[derive(Default)]
#[serde(default)]
struct MySubsystem {
enable_feature1: bool,
enable_feature2: bool,
}
```
If a specific field needs a specialized default -- such as `enable_feature1` needing a default value of `true` -- avoid using `#[derive(Default)]` with `#[serde(default = "default_true")]`. This is a common source of bugs. See below for an example:
```
#[derive(Default)]
struct MySubsystem {
#[serde(default = "default_true")]
enable_feature1: bool,
enable_feature2: bool,
}
fn default_true -> bool {
true
}
```
If the user provides a fully empty platform config `{}`, `enable_feature1` will be populated using the `#[derive(Default)]` which defaults to `false`.
If the user instead provides `{ "my_subsystem": {} }`, `enable_feature1` will be populated using `default_true()` which returns `true`.
Use `impl Default` to keep the deserialization consistent no matter what the user provides.
```
impl Default for MySubsystem {
fn default() -> Self {
Self {
enable_feature1: true,
enable_feature2: false,
}
}
}
```
## Use `Option<bool>` sparingly
Tri-state fields are often mis-interpreted and should only be used when their `None` value has an obvious meaning.
```
#[derive(Default)]
struct MySubsystem {
enable_feature: Option<bool>,
}
```
In the above example, it is not clear what the difference is between:
* `enable_feature = None`
* `enable_feature = Some(false)`
`enable_feature` could be changed to a `bool` without losing functionality.
One use-case that requires `Option<bool>` is if the default value of the field should change depending on other configuration values, such as the build type:
```
let enable_feature = match (build_type, my_subsystem.enable_feature) {
// If nothing is provided, the default value is determined
// based on the build type.
(Eng, None) => true,
(UserDebug | User, None) => false,
// Otherwise, we use the value provided by the user.
(_, Some(enabled)) => enabled,
};
```