Module transforms

Module transforms 

Source
Expand description

Request/response transformation grammar (DW-028): the config shapes for header/query manipulation and JSON-pointer body transforms, the shared JSON-Pointer grammar validation and the runtime agree on, and the security-header injection policy (feature analysis 4.12 and the 5-Security “security header injection” row).

This is config-contract grammar in the same sense as net.rs (the IP/CIDR vocabulary) and versioning.rs (the HTTP-date and media-type vocabularies): validation (snapshot::validate) and the runtime (dataplane::transforms) must agree on ONE parsing of these strings, so the grammar lives in config, the lowest consuming domain.

§The streaming contract (the issue’s done-when)

“Streaming is preserved unless a transform explicitly buffers.” The surface splits exactly along that line:

  • Header and query ops, and security headers, never touch a body: streaming responses (SSE, upgrades, chunked downloads) pass with zero buffering, exactly as before.
  • A JSON body transform is the one EXPLICITLY BUFFERING transform: it opts the route in, applies only to JSON bodies (content-type gated), and buffers at most max_bytes — a hard cap enforced both against a declared Content-Length and against the live stream. Over-cap bodies fail CLOSED (413 request-side, 502 response-side), never “transform what fit” and never silently pass through: a skipped policy transform is a fail-open data leak in the masking direction (DW-029 builds on this machinery).

§Pointer semantics (why strict)

A set/remove whose JSON Pointer does not resolve at runtime is an ERROR, not a skip. Pointer misses are schema drift, and in the redaction direction (remove the secret field) a silent miss is exactly the leak the policy exists to prevent. Strictness costs operators a 400/502 on drifted contracts; leniency would cost them their data. The error names the offending pointer server-side only (the client envelope carries a generic message).

§Security headers vs the decoration tail

Security headers are an EDGE property of every response the route emits — action responses and gateway short-circuits alike (a 401 without X-Content-Type-Options: nosniff is a real, if minor, gap; contrast deprecation stamps, which announce API lifecycle and are deliberately absent from short-circuits). They REPLACE any upstream-sent values: the gateway is the source of truth for its edge policy, the same rule as the deprecation and X-RateLimit-* headers. They apply last in the response decoration tail, after operator transforms: an operator who needs per-route-exception behavior omits the field here and sets it via transforms.

§Response field masking (DW-029) — the security sharp edge

Masking redacts JSON fields on the way OUT, per consumer group (the mass-assignment/data-leak guard). It reuses the buffering and pointer machinery above with ONE posture change: every gate that the DW-028 body transform treats as pass-through (an already-encoded body, a non-JSON content type) is, for masking, a REJECTION — the gateway cannot prove fields absent from bytes it cannot parse, and a skipped masking policy is exactly the leak the policy exists to prevent. Over-cap, invalid JSON, and unresolved pointers fail the same way (502), the strictness the pointer rules above already argue for. Masking runs FIRST in the response decoration tail — before the DW-028 body transform and before compression (DW-027) — so no later stage can resurrect a redacted value: once the sentinel replaces the secret, the original bytes exist nowhere in the gateway.

Structs§

BodyTransform
A body transform (DW-028). The json variant is the whole of v1: any other body transform family (templating, XML) would be a new variant here — the wrapper exists so adding one does not reshape the config surface.
CompiledJsonTransform
Snapshot-compiled form of a JsonBodyTransform (DW-028): every op’s pointer parsed once at snapshot-compile time; the request path applies tokens, never re-parsing config strings. Validation guarantees every pointer parses, so compilation drops nothing on any publishable config; a None-producing parse is skipped with the same unreachable-skip contract as the CORS/compression compilations.
CompiledMasking
Snapshot-compiled form of a Masking policy (DW-029): every pointer parsed once at snapshot-compile time. The effective pointer set is resolved per request (the floor plus the authenticated consumer’s groups — the union rule), which is why groups stay a map here rather than being flattened at compile time.
HeaderOps
Header manipulation ops (DW-028). Applied in ONE deterministic order regardless of YAML key order: set (replace all values of the name with one value), then add (append one value, keeping any existing values), then rename (move every value of from to to), then remove (drop every value of the name) — so remove can clean up what earlier ops placed, and rename sees both upstream and just-set values. Maps iterate in sorted key order (BTreeMap), making multi-entry application deterministic.
JsonBodyTransform
JSON-pointer body transform (DW-028): RFC 6901 pointers against a parsed JSON document, applied in listed order (each op sees the previous op’s result). THIS IS THE EXPLICITLY BUFFERING TRANSFORM — the only place in the transforms surface that reads a body. Hard rules, enforced by dataplane::transforms:
JsonPointer
A parsed RFC 6901 JSON pointer (DW-028): the compiled form the runtime applies and validation checks, so the two can never disagree on what /items/0 means. Built once at snapshot compile; the raw config string never reaches the request path (the same precompute contract as the CORS/compression/deprecation tables).
Masking
Response field masking policy (DW-029, feature analysis 5-Security “response field masking”): RFC 6901 pointers whose values are replaced with the fixed MASKED_VALUE sentinel on the route’s responses, before any other body-handling stage. The mass-assignment/data-leak guard: a field named here NEVER reaches the client, whatever the upstream put in it.
QueryOps
Query-string manipulation ops (DW-028), applied to the forwarded request’s query. The original query is split into pairs WITHOUT decoding (each pair’s key and value stay byte-verbatim, so untouched parameters — including their exact percent-encoding — survive); only pairs a named op touches are re-encoded (new values are percent-encoded by the gateway). Ops apply in the same deterministic order as header ops: set, add, rename, remove.
RequestTransforms
Request-side transforms (DW-028).
ResponseTransforms
Response-side transforms (DW-028), applied to the route’s action responses (proxy, redirect, respond) — not to gateway short-circuits (limits, authn/authz, rate limits, maintenance, sheds), which describe the request, not the upstream’s output.
SecurityHeaders
Security-header injection policy (DW-028, feature analysis 5-Security “security header injection”). Each field that is present injects (REPLACING any upstream-sent value — the gateway is the source of truth at its edge) exactly one standard hardening header on every response the route emits, including gateway short-circuits (401/403/413/429/503, CORS preflights): unlike deprecation stamps, these harden every byte the edge sends, and a browser parsing an error page deserves the same guarantees as one parsing a 200.
Transforms
Route-scoped request and response transforms (DW-028, feature analysis 4.12). Absent (the default): the gateway forwards bytes and headers untouched. Presence opts the route’s traffic into the declared manipulations — nothing here applies to any other route.

Enums§

CompiledJsonOp
One compiled op.
FrameOptions
The X-Frame-Options value (DW-028).
JsonOp
One JSON-pointer operation (DW-028). set writes any JSON value at the pointer (creating the final key in an object parent that must already exist; replacing an existing element in an array parent); remove deletes the addressed value. The ROOT pointer ("") is valid for set (whole-document replacement) and rejected for remove by validation (a body with no document is not a state this transform can produce).
JsonTransformError
Why a compiled transform failed on a document (DW-028). The offending pointer rides along for the server-side log; the client envelope stays generic.

Constants§

MASKED_VALUE
The value a masked field is replaced with (DW-029): the JSON string "***" (the feature analysis row’s JSON paths -> ***). FIXED, not configurable — the sentinel is a fixed marker the client can rely on (a value the gateway itself chose and documents) and identical on every route, so nothing about a masked response depends on per-route config a client cannot see. One ambiguity is inherent and documented: source data that is literally "***" at a masked position is indistinguishable from the sentinel — treat every "***" at a configured pointer as masked (the operator docs carry the same caveat). Operators who need a different shape on a specific route combine masking with a DW-028 response transform (which runs after masking and sees the sentinel).

Functions§

is_forbidden_request_header
Header names no REQUEST-side header op may touch (DW-028). Framing (content-length, transfer-encoding) is rebuilt by the forward pipeline from the actual body — an op that forced a disagreeing value would be a request-smuggling primitive aimed at the upstream, the exact class the pre-parse sniff exists to stop on the inbound side. The rest is the hop-by-hop class (RFC 9110 section 7.6.1 plus the legacy keep-alive/proxy spellings): the gateway strips and, for upgrades, re-adds connection/upgrade itself. host is not the operator’s either — the gateway names the origin it dials (see UpstreamHandle::send_with_hash_key).
is_forbidden_response_header
Header names no RESPONSE-side header op may touch (DW-028): the framing/hop-by-hop class plus content-encoding, which only the compression pipeline may manage — an op that stripped it without decoding would corrupt the body, and one that added it would misdescribe bytes it did not encode.
is_json_media_type
Is media_type (the part before any ;, already lowercased) a JSON media type this gateway will body-transform (DW-028)? Exactly the application/json type and the structured-syntax suffix family application/<*+json> (an application/vnd.acme+json body IS a JSON body); parameters are the caller’s business (stripped before this check). No other type family carries the +json suffix in practice, and widening to text/json-style one-offs buys nothing.