Skip to content

Commit

Permalink
initial documentation draft
Browse files Browse the repository at this point in the history
  • Loading branch information
MattSturgeon committed Aug 16, 2024
1 parent 654bd51 commit 7c16803
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 2 deletions.
43 changes: 43 additions & 0 deletions nixos/doc/manual/development/freeform-modules.section.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Freeform modules {#sec-freeform-modules}

<!-- TODO: Consider re-writing this doc to favor `types.record` over `types.submodule` -->

Freeform modules allow you to define values for option paths that have
not been declared explicitly. This can be used to add attribute-specific
types to what would otherwise have to be `attrsOf` options in order to
Expand All @@ -12,6 +14,47 @@ into the resulting `config` set. Since this feature nullifies name
checking for entire option trees, it is only recommended for use in
submodules.

<!-- TODO: Link to more details on the record and submodule types -->

Wildcard records can also be used to achieve the same thing, and may be
preferred in many scenarios due to their improved performance when
compared to submodules and also the ability to declare "optional" fields.

Wildcards records are used by simply passing `wildcard = types.anything`
to a record type definition.

::: {#ex-wildcard-record .example}
### Wildcard record

Most freeform submodules can also be represented as wildcard records.

The following example is equivalent to the first submodule example:

```nix
{ lib, config, ... }: {
options.settings = lib.mkOption {
type = lib.types.record {
wildcard = lib.types.str;
# We want this attribute to be checked for the correct type
fields.port = lib.mkOption {
type = lib.types.port;
# Declaring the option also allows defining a default value
default = 8080;
};
# We could use an "optional" field, instead of supplying a default
optionalFields.port = lib.mkOption {
type = lib.types.port;
};
};
};
}
```

::: {#ex-freeform-module .example}
### Freeform submodule

Expand Down
64 changes: 62 additions & 2 deletions nixos/doc/manual/development/option-types.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,27 @@ merging is handled.
definitions cannot be merged. The regular expression is processed
using `builtins.match`.

## Submodule types {#sec-option-types-submodule}
## Record and module types {#sec-option-types-submodule}

Submodules are detailed in [Submodule](#section-option-types-submodule).
Records and submodules are detailed in [Record](#section-option-types-record) and [Submodule](#section-option-types-submodule) respectively.
While submodules provide the full power of the module system, records are usually faster to evaluate and may be preferred in many scenarios.

`types.record` { `fields` ? {}, `optionalFields` ? {}, `wildcard` ? null }.

: A set of sub options, represented as fields. A field can be almost
any option, including record or submodule type options.
It has parameters:

- *`fields`* An attribute set of options that will be merged into
the final value.

- *`optionalFields`* An attribute set of options that will only be
merged into the final value _if_ they are defined.

- *`wildcard`* An option-type used to merge unknown field
definitions into the final value.

If null, definitions for unknown fields will throw an error.

`types.submodule` *`o`*

Expand Down Expand Up @@ -417,6 +435,48 @@ Composed types are types that take a type as parameter. `listOf
value of type *`to`*. Can be used to preserve backwards compatibility
of an option if its type was changed.

## Record {#section-option-types-record}

<!-- TODO: Add some examples!! -->

Records are a simpler alternative to [submodules](#section-option-types-submodule).
Rather than merging fully-fledged modules, a record consists only of "field" sub-options and an optional "wildcard" type, similar to a submodule's `freeformType`.

::: {.note}
Because records are not implemented using the full module system, they can usually be evaluated significantly faster than submodules.
:::

### Record fields

A record's sub-options are declared as `fields`. A field can be almost any option (restrictions listed below).
You can also use other record or submodule options in a record's fields to implement "nested" options.

#### Restrictions

- Fields must be options (e.g. created using `lib.mkOption`)
- Fields cannot have an `apply` function
- Fields cannot be `readOnly`

### Record optional fields

In addition to _required_ fields, a record can contain "optional" fields. Optional fields are similar to wildcard or freeform types, in that they are only merged into the final value when defined.
This contrasts "required" fields and submodule sub-options, which will still be merged into the final value when undefined as a stub value that throws a "used but not defined" error when read.

Optional fields are a unique feature, not currently supported by submodules.

### Restrictions
- All the same restrictions for required fields apply
- Additionally, optional fields cannot have a `default`

### Record wildcard type

Records can optionally be declared with a "wildcard" type, which can be used to allow definitions that do not match the explicitly declared "field" options.
Any definition that does not match a field option will be checked against the "wildcard" type, for example you could set `wildcard = types.anything`.

::: {.info}
A wildcard record with no fields can be thought of as equivialent to `types.attrsOf`!
:::

## Submodule {#section-option-types-submodule}

`submodule` is a very powerful type that defines a set of sub-options
Expand Down

0 comments on commit 7c16803

Please sign in to comment.