Skip to main content

kvp_entries

Function kvp_entries 

Source
pub(crate) fn kvp_entries<F>(params: &[KeyValuePair], render: F) -> FieldValue
where F: FnMut(u64, &KvpValue) -> (Option<&'static str>, Option<FieldValue>),
Expand description

Render a Key-Value-Pair list as entries, in the order the wire carried them.

§Why a list and not a map keyed by name

Every draft models a parameter block as an ordered list of (Type, Value), with types ascending. Rendering it as a map keyed by the draft’s name for each type reads better and loses three things:

  • Repeats. Two parameter definitions permit a message to carry their type more than once — AUTHORIZATION_TOKEN, and on drafts 19 and 20 the five Range Filters. A map has one slot per name, so two SUBGROUP_FILTERs under different SetIDs became the second one alone: the frame said “Subgroup 1-3 in set 0, or 10-12 in set 1” and the record said “10-12”, which is not a narrower reading of the request but a different one.
  • Order. Drafts 16 and later require that “Parameters MUST be serialized in ascending order by Type” and answer a descending pair with a session close. A map has no order, so no vector could state that rule at all.
  • Unknown types. A map has no name to key them under, so each draft invented something: 11 through 14 dropped them, and the later ones parked them in a second, differently-shaped unknown array beside the named ones. Two containers for one wire field.

An entry list has none of those problems and needs no special case for any of them: a repeat is two entries, order is the list’s, and an unknown type is an entry without a name.

§The entry

type is always present, as the lowercase hex of the Parameter Type. name is present when the draft names that type. Then exactly one of:

  • value — the decoded value, when this codec models it. A varint renders as its number, a structure as a nested map.
  • raw_hex — the value’s bytes, when it does not.

An unnamed varint parameter gets value rather than raw_hex, because a varint’s value is its content and there are no bytes to show. length is the key that does not belong here: it would hold the varint’s value under a name that promises the byte count of something else.

§Why the allow, and what it cannot hide

Every caller is a parameter renderer, and every parameter renderer belongs to a draft: fields::params for drafts 07 through 10, draftNN::fields for 11 through 20. So the build that compiled no draft has this function and nothing that calls it, and that build is a standing CI row twice over — just test-features’s no drafts clippy, and the two zero-draft rows of just draft-matrix, all three under -D warnings.

A cfg here would have to name all fourteen drafts, and it would then have to be repeated on this module’s own unit tests, which call this function and name no draft at all: --all-targets compiles them, so gating the function without gating them turns a dead-code warning into a build failure in the very row it was meant to fix. The allow is one line and has no second copy to fall out of step with.

It conceals nothing in a build that has a draft. One draft is enough to give this a caller, so the lint is live on all fourteen single-draft rows and on every build a user will ever make; the allow is inert everywhere except the build where the function is correctly unused.