You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Collapse the Config/Request type pairs in natmap models #42
Part of the whole-repo over-engineering audit (2026-09-30).
What to build
One type per rule kind on the wire. The *Request twins disappear and the axum handlers take the *Config type directly.
Findings
DnatConfig and DnatRequest have byte-identical field lists. So do SnatConfig/SnatRequest, HairpinConfig/HairpinRequest, and PolicyRouteConfig/PolicyRouteRequest, the last pair identical down to the #[derive] line. All four *Config types already derive Deserialize. The cost is four From impls (44 lines of pure field copying) plus seven hand-written field-by-field copy sites in crates/natmap/src/api.rs.
This is Duplicated Knowledge with a Primitive Obsession flavour: the wire shape has two names, and nothing about the pair is a real distinction (a request is a config, and a config is a request).
Acceptance criteria
DnatRequest, SnatRequest, HairpinRequest and PolicyRouteRequest are gone, along with their four From impls
Every handler in crates/natmap/src/api.rs takes Json<*Config> directly
The four NatmapClient methods that took a *Request now take the *Config
The JSON on the wire is byte-identical before and after
cargo test -p lab-ops_natmap passes; crates/natmap/tests/model.rs and the api.rs request tests still cover the same serialisation paths
No custom error type introduced; color_eyre::Result per standards.md §4.1
Acceptance-criteria deviation: lan_cidr no longer serialises as null
Found in review of #42. The criterion "The JSON on the wire is byte-identical before and after" is not met for one field, and it is recorded here rather than left implicit.
HairpinRequest (deleted) vs HairpinConfig (the merged replacement):
// before — HairpinRequest#[serde(default)]pub lan_cidr:Option<String>,// after — HairpinConfig#[serde(default, skip_serializing_if = "Option::is_none")]pub lan_cidr:Option<String>,
The wire delta
lan_cidr: None (the common case — lan_cidr only applies to preserve_src_ip hairpin):
// before
{"ext_ip":"203.0.113.50","int_ip":"10.0.0.99","ports":"8080","proto":"tcp","lan_cidr":null}
// after
{"ext_ip":"203.0.113.50","int_ip":"10.0.0.99","ports":"8080","proto":"tcp"}
This is the only serialisation delta in #42: skip_serializing_if appears exactly once in crates/natmap/src/models.rs:199.
No functional break — verified, not assumed
The deserialise side has a default. Both types carry #[serde(default)], so an absent lan_cidr key deserialises to None exactly as an explicit null does. A probe test confirmed the two forms produce equal values:
Nothing asserts the literal null.rg -n 'null' crates/natmap/ tests/ returns only 2>/dev/null shell redirects and Consul jq null-guards — no test asserts a JSON null in a hairpin body.
Decision: accept the delta
Keeping the one-type-per-rule shape that #42 exists to produce is worth more than reproducing a redundant null key. Re-adding a serialisation attribute purely to re-emit "lan_cidr": null would reintroduce exactly the kind of noise this pass is deleting, and both ends of this workspace ship together. The omission is also the better wire format.
skip_serializing_if is left in place. Flagging so the deviation is auditable rather than hidden.
Implemented in 50db2ad (refactor) and 1c7ffe9 (docs corrections).
Cut dead code and unused dependencies #40 — the deletions, the 8 manifest dependencies, and the write-only --state-dir flag. The flag's 15 Docker-test invocations went with it; docs/dev/testing.md:142 was corrected, since the flag was never providing the isolation it claimed.
Extract the repeated natmap api and table helpers #43 — the err(), header() and push_port_args helpers, the verb-taking route/rule builders, the single runtime, to_lowercase gone, and the unreachable protocol arm removed.
Deviation from #42's "byte-identical" criterion:POST/DELETE /hairpin now omits lan_cidr instead of sending null, because HairpinConfig carries skip_serializing_if and the deleted HairpinRequest did not. Verified empirically that absent and explicit null both deserialise to None, and that no test asserts a JSON null. Accepted deliberately rather than reintroducing a redundant null key.
Verified:cargo clippy --workspace --all-targets --all-features --no-deps clean; cargo test -p lab-ops_natmap (189), -p lab-ops_auto-discover (67), -p lab-ops_lab-lib (35), -p lab-ops --lib (65) all pass. The iptables argv tests have no diff, so the generated rules are provably unchanged. A review pass found no remaining defects.
Not run: the 122+ Docker integration suite, per AGENTS.md. It compiles; you trigger it. ./dev.sh test or ./dev.sh all.
Activity
FAZuH commentedon Sep 30, 2026
Acceptance-criteria deviation:
lan_cidrno longer serialises asnullFound in review of #42. The criterion "The JSON on the wire is byte-identical before and after" is not met for one field, and it is recorded here rather than left implicit.
HairpinRequest(deleted) vsHairpinConfig(the merged replacement):The wire delta
lan_cidr: None(the common case —lan_cidronly applies topreserve_src_iphairpin):lan_cidr: Some(..)is unchanged:{"ext_ip":"203.0.113.50","int_ip":"10.0.0.99","ports":"8080","proto":"tcp","lan_cidr":"192.168.1.0/24"}This is the only serialisation delta in #42:
skip_serializing_ifappears exactly once incrates/natmap/src/models.rs:199.No functional break — verified, not assumed
The deserialise side has a default. Both types carry
#[serde(default)], so an absentlan_cidrkey deserialises toNoneexactly as an explicitnulldoes. A probe test confirmed the two forms produce equal values:Nothing asserts the literal
null.rg -n 'null' crates/natmap/ tests/returns only2>/dev/nullshell redirects and Consuljqnull-guards — no test asserts a JSONnullin a hairpin body.Decision: accept the delta
Keeping the one-type-per-rule shape that #42 exists to produce is worth more than reproducing a redundant
nullkey. Re-adding a serialisation attribute purely to re-emit"lan_cidr": nullwould reintroduce exactly the kind of noise this pass is deleting, and both ends of this workspace ship together. The omission is also the better wire format.skip_serializing_ifis left in place. Flagging so the deviation is auditable rather than hidden.FAZuH commentedon Sep 30, 2026
Implemented in
50db2ad(refactor) and1c7ffe9(docs corrections).--state-dirflag. The flag's 15 Docker-test invocations went with it;docs/dev/testing.md:142was corrected, since the flag was never providing the isolation it claimed.*Requesttwins removed, handlers now take*Config. One wire delta, recorded below.err(),header()andpush_port_argshelpers, the verb-taking route/rule builders, the single runtime,to_lowercasegone, and the unreachable protocol arm removed.Deviation from #42's "byte-identical" criterion:
POST/DELETE /hairpinnow omitslan_cidrinstead of sendingnull, becauseHairpinConfigcarriesskip_serializing_ifand the deletedHairpinRequestdid not. Verified empirically that absent and explicitnullboth deserialise toNone, and that no test asserts a JSONnull. Accepted deliberately rather than reintroducing a redundant null key.Verified:
cargo clippy --workspace --all-targets --all-features --no-depsclean;cargo test -p lab-ops_natmap(189),-p lab-ops_auto-discover(67),-p lab-ops_lab-lib(35),-p lab-ops --lib(65) all pass. The iptables argv tests have no diff, so the generated rules are provably unchanged. A review pass found no remaining defects.Not run: the 122+ Docker integration suite, per
AGENTS.md. It compiles; you trigger it../dev.sh testor./dev.sh all.Not pushed.