tree: d26ec63a38fbbf0a28495509bdd0b018c3b91357 [path history] [tgz]
  1. src/
  2. BUILD.gn
  3. README.md
src/lib/assembly/config_schema/README.md

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,
};