Skip to main content

Module query_string

Module query_string 

Source
Expand description

Structured query-string decoding for Query<T> (issue #1972).

See the extractors guide for the narrative version, with worked examples.

§Why this exists

Query<T> used to delegate straight to serde_urlencoded, which is strictly flat: it decodes ?q=foo&page=2 and nothing else. A Vec<String> field fed the repeated-key form ?tags=a&tags=b failed with invalid type: string "a", expected a sequence, and a nested struct field was unrepresentable by any encoding. That left MCP tool contracts unhonorable — tools/call dispatch renders an array query argument as repeated keys, so a tool whose inputSchema advertised tags: array dispatched a request its own handler rejected — and pushed builders onto comma-separated strings and JSON-in-a-string fields.

§Wire format

The decoder is a superset of the flat form: a query string with unique scalar keys decodes exactly as it did before. On top of that it accepts a bracketed dialect whose items[0][sku] shape matches the repeated-row encoding nested_form renders — generalized here to arbitrary objects, sequences and depths. Note this applies to the query string only. Request bodies use flat keys: Form<T> decodes through serde_urlencoded, and ChangesetForm and NestedChangesetForm decode through from_urlencoded (repeated keys fill a Vec, brackets stay part of the key).

q=foo                       // scalar
page=2                      // scalar, coerced to the field's type
tags=a&tags=b               // repeated key   → sequence ["a", "b"]
tags[]=a&tags[]=b           // append form    → sequence ["a", "b"]
tags[0]=a&tags[2]=c         // indexed form   → sequence ["a", "c"]
filter[status]=open         // nested object  → { status: "open" }
items[0][sku]=A-1           // array of objects

Percent-encoded brackets (%5B / %5D) are the same thing: keys are percent-decoded before parsing, so a client that encodes them round-trips identically.

§Deliberate semantics

  • Scalar coercion matches serde_urlencoded. Values arrive as text and are parsed with str::parse into the field’s type, so page=2 fills a u32 and flag=true fills a bool. An Option<T> field that is present but empty (?page=) still visits Some, exactly as serde_urlencoded does — only an absent key is None.

  • A duplicated key is an error in a single-valued position. ?q=a&q=b against a String field is rejected — serde_urlencoded + serde’s derive rejected it too (duplicate field), and quietly picking one of two values is how parameter-pollution bugs are built. A sequence field takes every occurrence; that is the whole point of the repeated-key form.

  • Errors never echo a value. A decode failure names the field path and the expected type, never the submitted text — the message is returned in the 400 body and recorded by error reporters, and a query parameter can hold a secret.

  • Shape conflicts poison one key, not the request. ?filter=flat&filter[status]=open uses one name as both a scalar and a container. That key is rejected if the target claims it; a target that ignores it (ad-tracking junk, a crawler’s garbage parameter) still decodes, exactly as it did when the key was merely unrecognised.

  • Malformed brackets stay literal. A key the bracket grammar cannot parse (weird[unclosed) is used verbatim as a flat key, so a stray bracket never turns into a parse failure.

  • Nesting is depth-capped at MAX_DEPTH and indices key an ordered map rather than a Vec, so neither deep nesting nor a huge index (tags[4000000000]=x) lets a request drive unbounded allocation.

  • Map iteration is key-ordered. The tree keys each level with a BTreeMap, so a Query<Vec<(String, String)>> target sees pairs sorted by key rather than in submission order (occurrences of one key keep their order). Deterministic either way, but not the wire order.

§Compatibility note

Brackets are now structure, not part of the key text. A target that types a parameter as a plain value — Query<HashMap<String, String>> is the common one — used to receive ?filter[a]=1 as the literal key "filter[a]"; it now sees a nested object and reports a decode error naming the fix. Type such a field as a nested struct, or as HashMap<String, serde_json::Value> to accept either shape.

Structs§

QueryError
A query-string decode failure, carrying the field path it occurred at.

Constants§

MAX_DEPTH
Maximum bracket nesting depth accepted in a query key.

Functions§

from_query_str
Decode a raw (still percent-encoded) query string into T.