Skip to content

Support for key aliases - #70

Merged
lukebakken merged 4 commits into
mainfrom
cuttlefish-69
Apr 12, 2026
Merged

lukebakken merged 4 commits into
mainfrom
cuttlefish-69

Conversation

@michaelklishin

@michaelklishin michaelklishin commented Apr 10, 2026 •

Copy link
Copy Markdown
Collaborator

This implements the design in #69 with a few deviations or additions:

  1. alias is a single string key property accepted as a common alternative to aliases, an array of strings
  2. This PR adds a few property-based test suites
  3. Conflicting aliases are considered to be schema integrity errors
  4. Conflicting canonical keys and aliases are resolved like so: the canonical (original) value wins
  5. Conflicting values on alias lists are resolved in an equally straightforward manner: the first value on the list wins
  6. {aliases, []} in a merge mapping is a deliberate "clear all aliases" directive - it clears aliases inherited from the base mapping. This is intentionally asymmetric with see and doc, where an empty list in a merge mapping means "not specified, inherit".

RHS Substitution Limitation

Alias keys are rewritten to their canonical keys early in the pipeline, before RHS substitutions are evaluated. This means $(old.key) in a substitution will fail if old.key is an alias - use $(new.key) instead. Cuttlefish will emit a specific error message identifying the alias and naming the canonical key to use.

A Realistic Example

In this hypothetical (for now anyway) scenario, assume that Team RabbitMQ
wants to move a few rabbitmq.conf keys under the raft.quorum_queue.* namespace (raft.{subsystem}.{key}, if we generalize).

Today this requires duplicating the mapping and writing boilerplate translations
for every key, and making sure you get it right every time:

{mapping, "raft.quorum_queue.segment_max_entries", "ra.segment_max_entries", [
    {datatype, integer},
    {validators, ["non_zero_positive_integer", "positive_16_bit_unsigned_integer"]}
]}.
{mapping, "raft.segment_max_entries", "ra.segment_max_entries", [
    {datatype, integer}
]}.
{translation, "ra.segment_max_entries", fun(Conf) ->
    case cuttlefish:conf_get("raft.quorum_queue.segment_max_entries", Conf, undefined) of
        undefined -> cuttlefish:conf_get("raft.segment_max_entries", Conf, cuttlefish:unset());
        Value     -> Value
    end
end}.

With aliases, the old keys just work and the implementation is dramatically less error-prone:

{mapping, "raft.quorum_queue.segment_max_entries", "ra.segment_max_entries", [
    {datatype, integer},
    {validators, ["non_zero_positive_integer", "positive_16_bit_unsigned_integer"]},
    %% a shortcut for {aliases, ["raft.wal_max_size_bytes"]}
    {alias, "raft.wal_max_size_bytes"}
]}.

A Basic Conflict Resolution Example

If both the canonical key and the alias are set, the canonical key takes precedence:

# rabbitmq.conf
raft.quorum_queue.wal_max_size_bytes = 536870912
raft.wal_max_size_bytes = 268435456
{mapping, "raft.quorum_queue.wal_max_size_bytes", "ra.wal_max_size_bytes", [
    {datatype, integer},
    {validators, ["non_zero_positive_integer"]},
    {alias, "raft.wal_max_size_bytes"}
]}.

The outcome: the value of 536870912 will be used, and Cuttlefish will emit a warning:

raft.wal_max_size_bytes is deprecated and ignored in favor of raft.quorum_queue.wal_max_size_bytes

Other Comments

Closes #69.

This also fixes a small bug and introduces
a single string `alias` key in addition to
a list of multiple `aliases`: the single key
version is very likely to be used substantially
more often.
When `$(old.key)` is used in a RHS substitution and `old.key` is a
deprecated alias that has been rewritten to its canonical key, the
previous error message said only that the config variable was not set,
with no hint that the key was a known alias.

Enrich the error in `map_value_sub/3` by post-processing the error
list: any `substitution_missing_config` error whose missing variable
is a known alias is rewritten to `substitution_alias_key`, which
names both the alias and the canonical key it maps to.

Add `xlate/1` clause for `substitution_alias_key` and a test that
asserts the formatted message mentions "alias".
"Resolving Aliases" was inconsistent with the surrounding log messages.
Change to "Resolving aliases".

@lukebakken lukebakken left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to ship 3.7 if you'd like, thanks!

@lukebakken
lukebakken merged commit b24a264 into main Apr 12, 2026
6 checks passed
@lukebakken
lukebakken deleted the cuttlefish-69 branch April 12, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for key (mapping) alises

3 participants