2022-01-17 17:16:31 +03:00
|
|
|
# Merging records
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
In Nickel, the basic building blocks for data are records (objects in JSON or
|
|
|
|
attribute sets in Nix). Merging is a fundamental built-in operation whose role
|
2022-03-01 12:17:04 +03:00
|
|
|
is to combine records togethers. Fields common to several records will be
|
|
|
|
themselves recursively merged if possible, following the semantics described in
|
|
|
|
this document.
|
|
|
|
|
|
|
|
Merging is useful to compose small and logical blocks into a potentially complex
|
|
|
|
final configuration, making it more manageable. A merge is performed by the `&`
|
|
|
|
operator.
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Merge is a **symmetric** operation (or, pedantically, commutative). In practice,
|
|
|
|
this means that order doesn't matter, and `left & right` is the same thing as
|
|
|
|
`right & left`. When the operands need to be distinguished, as we will see for
|
|
|
|
default values for example, the idea is to use metadata to do so (annotations),
|
|
|
|
rather than relying on the left or right position.
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
**Warning**: At the time of writing, Nickel's version is 0.1. Important
|
|
|
|
additions to merging are planned for coming versions, including priorities and
|
|
|
|
custom merge functions. They are not detailed here yet. For more details, see
|
|
|
|
the associated technical document [RFC001][rfc001].
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
The section describes the behavior and use-cases of merge, by considering the
|
|
|
|
following situations:
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
- [Merging two records without common fields](#simple-merge-(no-common-fields))
|
|
|
|
- [Merging records with common fields](#recursive-merge-(with-common-fields))
|
|
|
|
- [Merging records with metadata](#merging-record-with-metadata)
|
|
|
|
* [Default values](#default-values)
|
|
|
|
* [Contracts](#contracts)
|
|
|
|
* [Documentation](#documentation)
|
|
|
|
- [Recursive overriding](#recursive-overriding)
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
## Simple merge (no common fields)
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Merging two records with no common fields results in a record with the fields
|
|
|
|
from both operands. That is, `{foo = 1, bar = "bar"} & {baz =false}` evaluates
|
|
|
|
to `{foo = 1, bar = "bar", baz = false}`.
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
### Specification
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Technically, if we write the left operand as:
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
2022-03-01 12:17:04 +03:00
|
|
|
left = {
|
2022-02-28 16:32:07 +03:00
|
|
|
field_left_1 = value_left_1,
|
|
|
|
..,
|
|
|
|
field_left_n = value_left_n,
|
|
|
|
}
|
|
|
|
```
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
And the right operand as:
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
|
|
|
right {
|
|
|
|
field_right_1 = value_right_1,
|
|
|
|
..,
|
|
|
|
field_right_k = value_right_k
|
|
|
|
}
|
|
|
|
```
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Then the merge `left & right` evaluates to the record:
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
|
|
|
{
|
|
|
|
field_left_1 = value_left_1,
|
|
|
|
..,
|
|
|
|
field_left_n = value_left_n,
|
|
|
|
field_right_1 = value_right_1,
|
|
|
|
..,
|
|
|
|
field_right_k = value_right_k
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
In other terms, `left & right` is the union of `left` and `right`.
|
|
|
|
|
|
|
|
### Examples
|
|
|
|
|
|
|
|
#### Split
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
You can split a configuration into subdomains:
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
2022-02-08 13:16:13 +03:00
|
|
|
// file: server.ncl
|
|
|
|
{
|
|
|
|
host_name = "example",
|
|
|
|
host = "example.org",
|
|
|
|
ip_addr = "0.0.0.0",
|
|
|
|
}
|
|
|
|
|
|
|
|
// file: firewall.ncl
|
|
|
|
{
|
|
|
|
enable_firewall = true,
|
|
|
|
open_ports = [23, 80, 443],
|
|
|
|
}
|
|
|
|
|
2022-02-22 14:07:26 +03:00
|
|
|
// file: network.ncl
|
|
|
|
let server = import "server.ncl" in
|
|
|
|
let firewall = import "firewall.ncl" in
|
2022-01-17 17:16:31 +03:00
|
|
|
server & firewall
|
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
This gives:
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
2022-02-22 14:07:26 +03:00
|
|
|
{
|
|
|
|
host_name = "example",
|
|
|
|
host = "example.org",
|
|
|
|
ip_addr = "0.0.0.0",
|
|
|
|
enable_firewall = true,
|
|
|
|
open_ports = [23, 80, 443],
|
|
|
|
}
|
|
|
|
```
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
#### Extension
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Given a configuration, you can use merge to add new fields:
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
// file: safe-network.ncl
|
|
|
|
let base = import "network.ncl" in
|
|
|
|
base & {use_iptables = true}
|
|
|
|
```
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
## Recursive merge (with common fields)
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
When the two operands have fields in common, those fields are recursively
|
|
|
|
merged. For example:
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
{
|
|
|
|
top_left = 1,
|
|
|
|
common = {left = "left"}}
|
|
|
|
& {
|
|
|
|
top_right = 2,
|
|
|
|
common = {right = "right"}
|
|
|
|
}
|
|
|
|
```
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Evaluates to the record
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
2022-01-25 00:07:09 +03:00
|
|
|
{
|
2022-02-28 16:32:07 +03:00
|
|
|
top_left = 1,
|
|
|
|
top_right = 2,
|
|
|
|
common = {left = "left", right = "right"}
|
2022-01-25 00:07:09 +03:00
|
|
|
}
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
When one or both of the common fields are not records, the merge will fail
|
|
|
|
unless one of the following condition hold:
|
|
|
|
|
|
|
|
- They are both of a primitive data type `Num`, `Bool`, `Enum`, or they are null, and they are equal
|
|
|
|
- They are physically equal, meaning that they point to the same location in
|
|
|
|
memory.
|
|
|
|
|
|
|
|
The rationale is that only equal values are merged, but for a notion of equality
|
|
|
|
that immediate to determine for the interpreter, and thus most restrictive than
|
|
|
|
`==`.
|
|
|
|
|
|
|
|
### Specification
|
|
|
|
|
|
|
|
```
|
2022-03-01 12:17:04 +03:00
|
|
|
left = {
|
2022-02-28 16:32:07 +03:00
|
|
|
field_left_1 = value_left_1,
|
|
|
|
..,
|
|
|
|
field_left_n = value_left_n,
|
|
|
|
common_1 = common_vleft_1,
|
|
|
|
..,
|
|
|
|
common_m = common_vleft_m,
|
2022-01-25 00:07:09 +03:00
|
|
|
}
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
And the right operand as:
|
|
|
|
|
|
|
|
```
|
|
|
|
right {
|
|
|
|
field_right_1 = value_right_1,
|
|
|
|
..,
|
|
|
|
field_right_k = value_right_k
|
|
|
|
common_1 = common_vright_1,
|
|
|
|
..,
|
|
|
|
common_m = common_vright_m,
|
|
|
|
}
|
2022-01-25 00:07:09 +03:00
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Where the `field_left_i` and `field_right_j` are distinct for all `i` and `j`.
|
|
|
|
Then the merge `left & right` evaluates to the record:
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
2022-01-25 00:07:09 +03:00
|
|
|
{
|
2022-02-28 16:32:07 +03:00
|
|
|
field_left_1 = value_left_1,
|
|
|
|
..,
|
|
|
|
field_left_n = value_left_n,
|
|
|
|
field_right_1 = value_right_1,
|
|
|
|
..,
|
|
|
|
field_right_k = value_right_k
|
|
|
|
common_1 = common_vleft_1 & common_vright_1,
|
|
|
|
..,
|
|
|
|
common_m = common_vleft_m & common_vright_m,
|
2022-01-25 00:07:09 +03:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
For two values `v1` and `v2`, if at least one value is not a record, then
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
|
|
|
v1 & v2 = v1 if (type_of(v1) is Num, Bool, Str, Enum or v1 == null)
|
|
|
|
AND v1 == v2
|
|
|
|
_|_ otherwise (indicates failure)
|
|
|
|
```
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
### Example
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
// file: udp.ncl
|
|
|
|
{
|
|
|
|
// same as firewall = {open_ports = {udp = [...]}},
|
|
|
|
firewall.open_ports.udp = [12345,12346],
|
|
|
|
}
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
// file: tcp.ncl
|
|
|
|
{
|
|
|
|
// same as firewall = {open_ports = {tcp = [...]}},
|
|
|
|
firewall.open_ports.tcp = [23, 80, 443],
|
|
|
|
}
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
// firewall.ncl
|
|
|
|
let udp = import "udp.ncl" in
|
|
|
|
let tcp = import "tcp.ncl" in
|
|
|
|
udp & tcp
|
|
|
|
```
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
In the above example, we merge two records, both with a field `firewall`. On
|
|
|
|
both sides, the value is a record, which is therefore merged. The same process
|
|
|
|
happens one layer below, on the common field `open_ports`, to result in the
|
|
|
|
final record:
|
2022-01-25 00:07:09 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
{
|
|
|
|
firewall = {
|
|
|
|
open_ports = {
|
|
|
|
udp = [12345, 12346],
|
|
|
|
tcp = [23, 80, 443],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-25 00:07:09 +03:00
|
|
|
```
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
## Merging record with metadata
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Metadata can be attached to values thanks to the `|` operator. Metadata
|
|
|
|
currently includes contract annotations, default value, and documentation. We
|
|
|
|
describe in this section how metadata interacts with merging.
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
### Default values
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
A `default` annotation can be used to provide a base value, but let it be
|
|
|
|
overridable through merging. For example, `{foo | default = 1} & {foo = 2}`
|
|
|
|
evaluates to `{foo = 2}`. Without the default value, this merge would have
|
|
|
|
failed with a `non mergeable fields` error, because merging being symmetric, it
|
|
|
|
doesn't know how to combine `1` and `2` in a generic and meaningful way.
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
#### Specification
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
We can consider the merging system to feature priorities. To each field
|
|
|
|
definition `foo = val` is associated a priority `p(val)`. When merging two
|
|
|
|
common fields `value_left` and `value_right`, then the results is either the one
|
|
|
|
with the highest priority (that overrides the other), or the two are tentatively
|
|
|
|
recursively merged, if the priorities are the same. Without loss of generality,
|
|
|
|
we consider the simple case of two records with only one field, which is the
|
|
|
|
same on both side:
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
|
|
|
{common = left} & {common = right}
|
|
|
|
= {
|
|
|
|
common = left if p(left) > p(right)
|
|
|
|
right if p(left) < p(right)
|
|
|
|
left & right if p(left) = p(right)
|
|
|
|
}
|
|
|
|
```
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Currently, there are only two priorities, `normal` (by default, when nothing is
|
|
|
|
specified) and the `default` one, with `default < normal`. We plan to add more
|
2022-03-01 12:17:04 +03:00
|
|
|
in the future (see [RFC001](https://github.com/tweag/nickel/blob/c21cf280dc610821fceed4c2caafedb60ce7177c/rfcs/001-overriding.md#priorities)).
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
#### Example
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Let us stick to our firewall example. Thanks to default values, we set the most
|
|
|
|
restrictive configuration by default, which can still be overridden if needed.
|
|
|
|
|
|
|
|
Let us first try without default values:
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
let base = {
|
2022-01-17 17:16:31 +03:00
|
|
|
firewall.enabled = true,
|
|
|
|
firewall.type = "iptables",
|
|
|
|
firewall.open_ports = [21, 80, 443],
|
|
|
|
} in
|
2022-02-28 16:32:07 +03:00
|
|
|
let patch = {
|
2022-01-17 17:16:31 +03:00
|
|
|
firewall.enabled = false,
|
|
|
|
server.host.options = "TLS",
|
|
|
|
} in
|
2022-02-28 16:32:07 +03:00
|
|
|
base & patch
|
|
|
|
```
|
|
|
|
|
|
|
|
Because merging is meant to be symmetric, Nickel is unable to know which value
|
|
|
|
to pick between `enabled = true` and `enabled = false` for the firewall, and
|
|
|
|
thus fail:
|
|
|
|
|
2022-01-17 17:16:31 +03:00
|
|
|
```
|
2022-02-28 16:32:07 +03:00
|
|
|
error: non mergeable terms
|
|
|
|
┌─ repl-input-0:2:22
|
|
|
|
│
|
|
|
|
2 │ firewall.enabled = true,
|
|
|
|
│ ^^^^ cannot merge this expression
|
|
|
|
·
|
|
|
|
7 │ firewall.enabled = false,
|
|
|
|
│ ^^^^^ with this expression
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
We can use default values to give the priority to the right side:
|
2022-01-17 17:16:31 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
let base = {
|
2022-01-17 17:16:31 +03:00
|
|
|
firewall.enabled | default = true,
|
2022-02-28 16:32:07 +03:00
|
|
|
firewall.type | default = "iptables",
|
|
|
|
firewall.open_ports | default = [21, 80, 443],
|
2022-01-17 17:16:31 +03:00
|
|
|
} in
|
2022-02-28 16:32:07 +03:00
|
|
|
let patch = {
|
2022-01-17 17:16:31 +03:00
|
|
|
firewall.enabled = false,
|
|
|
|
server.host.options = "TLS",
|
|
|
|
} in
|
2022-02-28 16:32:07 +03:00
|
|
|
base & patch
|
|
|
|
```
|
|
|
|
|
|
|
|
This evaluates to:
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
firewall = {
|
|
|
|
enabled = false,
|
|
|
|
open_ports = [21, 80, 443],
|
|
|
|
type = "iptables",
|
|
|
|
},
|
|
|
|
server = {
|
|
|
|
host = {
|
|
|
|
"options": "TLS"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-17 17:16:31 +03:00
|
|
|
```
|
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
### Contracts
|
2022-02-28 16:32:07 +03:00
|
|
|
|
|
|
|
*Note*: see the [correctness section](./correctness.md) and the
|
|
|
|
[contracts section](./contracts.md) for a thorough introduction to contracts in
|
|
|
|
Nickel.
|
2022-01-17 19:27:46 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Fields may have contracts attached, either directly, as in `{foo | Num = 1}`, or
|
|
|
|
propagated from an annotation higher up, as in `{foo = 1} | {foo | Num}`. In
|
|
|
|
both cases, `foo` must satisfy the contract `Num`. What happens if the value of
|
|
|
|
`foo` is altered in a subsequent merge? For example:
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
- Should `{foo | default | Num = 1} & {foo = "bar"}` succeed, although `foo`
|
|
|
|
would be a string in the final result?
|
|
|
|
- Should `{foo.subfield | Str = "a"} & {foo.other_subfield = 1}`
|
|
|
|
succeed, although a closed contract `{subfield | Str}` is attached to `foo`,
|
|
|
|
and the final result would have an additional field `other_subfield` ?
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Nickel chooses to answer **no** to both. In general, when a contract is attached
|
|
|
|
to a field `foo`, merging ensures that whatever is this field merged with,
|
|
|
|
including being dropped in favor of another value, the final value for `foo` has
|
|
|
|
to respect the contract as well or the evaluation will fail accordingly.
|
2022-01-17 19:27:46 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
#### Specification
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-03-01 17:04:40 +03:00
|
|
|
For two operands with one field each, which is the same on both side, with respective
|
2022-02-28 16:32:07 +03:00
|
|
|
contracts `Left1, .., Leftn` and `Right1, .., Rightk` attached:
|
|
|
|
|
|
|
|
```nickel
|
|
|
|
left = {
|
|
|
|
common | Left1
|
|
|
|
| ..
|
|
|
|
| Leftn
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
And
|
|
|
|
|
|
|
|
```nickel
|
|
|
|
right = {
|
|
|
|
common | Right1
|
|
|
|
| ..
|
|
|
|
| Rightk
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Then the `common` field of `left & right` will be checked against `Left1, ..,
|
|
|
|
Leftn, Right1, .., Rightk`. Here, we ignore the case of type annotations such as
|
|
|
|
`common: LeftType` that can just be considered as an additional contract
|
|
|
|
`Left0`.
|
2022-02-22 14:07:26 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
#### Example
|
2022-01-17 19:27:46 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
let Port
|
|
|
|
| doc "A valid port number"
|
|
|
|
= contract.from_predicate (fun value =>
|
|
|
|
builtin.is_num value &&
|
|
|
|
value % 1 == 0 &&
|
|
|
|
value >= 0 &&
|
|
|
|
value <= 65535) in
|
|
|
|
let GreaterThan
|
|
|
|
| doc "A number greater than the paramater"
|
|
|
|
= fun x => contract.from_predicate (fun value => value > x) in
|
|
|
|
|
|
|
|
{
|
|
|
|
port | GreaterThan 1024
|
|
|
|
| default = 8080,
|
|
|
|
} & {
|
|
|
|
port | Port = 80,
|
|
|
|
}
|
2022-01-17 19:27:46 +03:00
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
This fails at evaluation:
|
2022-01-17 19:27:46 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```
|
|
|
|
error: contract broken by a value.
|
|
|
|
[..]
|
|
|
|
┌─ repl-input-1:16:19
|
|
|
|
│
|
|
|
|
16 │ port | Port = 80,
|
|
|
|
│ ^^ applied to this expression
|
|
|
|
|
|
|
|
note:
|
|
|
|
┌─ repl-input-1:13:12
|
|
|
|
│
|
|
|
|
13 │ port | GreaterThan 1024
|
|
|
|
│ ^^^^^^^^^^^^^^^^ bound here
|
|
|
|
```
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-01-17 19:27:46 +03:00
|
|
|
|
2022-03-01 12:17:04 +03:00
|
|
|
### Documentation
|
2022-02-22 22:27:04 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Documentation is attached via the `doc` keyword. Documentation is propagated
|
|
|
|
during merging. For example, querying `foo` by using the command `nickel -f
|
|
|
|
config.ncl query foo` on:
|
2022-01-18 18:38:52 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
// config.ncl
|
2022-01-18 18:38:52 +03:00
|
|
|
{
|
2022-02-28 16:32:07 +03:00
|
|
|
foo | doc "Some documentation"
|
|
|
|
| = {}
|
2022-01-18 18:38:52 +03:00
|
|
|
} & {
|
2022-02-28 16:32:07 +03:00
|
|
|
foo.field = null,
|
|
|
|
}
|
2022-01-18 18:38:52 +03:00
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Will print `"Some documentation"` as expected. If both sides have documentation, the behavior
|
|
|
|
is unspecified, as merging two distinct blobs of text doesn't always make sense
|
|
|
|
in general. Currently, Nickel will randomly keeps one of the two in practice.
|
|
|
|
|
|
|
|
## Recursive overriding
|
|
|
|
|
|
|
|
We've seen in the section on default values that they are useful to override
|
|
|
|
(update) a single field with a different value. The combo of merging and default
|
|
|
|
values can do more. In Nickel, records are recursive by default, in order to
|
|
|
|
express easily dependencies between the different fields of the configuration.
|
|
|
|
Concretely, you can refer to other fields of a record from within this record:
|
2022-02-22 22:27:04 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
let base_config = {
|
|
|
|
version | default = "20.09",
|
2022-03-01 12:17:04 +03:00
|
|
|
input.url | default = "nixpkgs/nixos-%{version}",
|
2022-02-28 16:32:07 +03:00
|
|
|
} in
|
|
|
|
base_config
|
|
|
|
```
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Here, we referred to `version` from the `input` field transparently. This
|
|
|
|
configuration evaluates to:
|
2022-02-08 13:16:13 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
{
|
|
|
|
version = "20.09",
|
|
|
|
input = {url = "nixpkgs/nixos-20.09"},
|
|
|
|
}
|
2022-02-09 16:29:11 +03:00
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Merging handles overriding on recursive record too. More precisely, when we
|
|
|
|
override the default value of `version`, *the fields that depend on `version` --
|
|
|
|
here, `input` -- will also be updated automatically*. For example, `base_config
|
|
|
|
& {version = "unstable"}` will evaluate to:
|
2022-02-09 16:29:11 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
```nickel
|
|
|
|
{
|
|
|
|
version = "unstable",
|
|
|
|
input = {url = "nixpkgs/nixos-unstable"},
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Currently, one can only use it on a field that have been marked as default. A
|
|
|
|
more ergonomic way of overriding is planned, and described in [RFC001][rfc001].
|
|
|
|
|
|
|
|
### Example
|
|
|
|
|
|
|
|
Here is another variation of recursive overriding on our `firewall` example:
|
|
|
|
|
|
|
|
```nickel
|
|
|
|
let security = {
|
|
|
|
firewall.open_proto.http | default = true,
|
|
|
|
firewall.open_proto.https | default = true,
|
|
|
|
firewall.open_proto.ftp | default = true,
|
|
|
|
firewall.open_ports = []
|
|
|
|
@ (if firewall.open_proto.ftp then [21] else [])
|
2022-03-01 12:17:04 +03:00
|
|
|
@ (if firewall.open_proto.http then [80] else [])
|
|
|
|
@ (if firewall.open_proto.https then [443] else []),
|
2022-02-28 16:32:07 +03:00
|
|
|
} in // => security.firewall.open_ports = [21, 80, 443]
|
|
|
|
security & {firewall.open_proto.ftp = false} // => firewall.open_ports = [80, 443]
|
2022-02-09 16:29:11 +03:00
|
|
|
```
|
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
Here, `security.firewall.open_ports` is `[21, 80, 443]`. But in the returned
|
|
|
|
configuration (let's call it `result`), `result.firewall.open_ports = [80, 443]`.
|
2022-02-09 16:29:11 +03:00
|
|
|
|
2022-02-28 16:32:07 +03:00
|
|
|
[rfc001]: https://github.com/tweag/nickel/blob/c21cf280dc610821fceed4c2caafedb60ce7177c/rfcs/001-overriding.md
|