Skip to content

Commit b95b6ac

Browse files
committed
📝 document qs 6.15.3 list limit behavior
Describe cumulative enforcement, numeric-key overflow, ValueError semantics, and bracketed comma groups across the changelog, public docs, option docstrings, and qs-codec skill.
1 parent 3d92215 commit b95b6ac

6 files changed

Lines changed: 65 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.6.1-dev
2+
3+
* [FIX] match Node `qs` 6.15.3 cumulative list-limit enforcement across duplicate-key combinations and mixed list merges
4+
* [FIX] reject oversized flat comma values before allocating their split lists or decoding their values when `raise_on_limit_exceeded` is enabled
5+
* [CHORE] add Node `qs` 6.15.3 regression coverage for unbalanced bracket keys and cyclic compaction
6+
17
## 1.6.0
28

39
* [CHORE] make `Undefined` internal by removing `qs_codec.Undefined` and its public documentation

README.rst

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Highlights
2525
- Pluggable hooks: custom ``encoder``/``decoder`` callables; options to sort keys, filter output, and control percent-encoding (keys-only, values-only).
2626
- Nulls & empties: ``strict_null_handling`` and ``skip_nulls``; support for empty lists/arrays when desired.
2727
- Dates: ``serialize_date`` for ISO 8601 or custom (e.g., UNIX timestamp).
28-
- Safety limits: configurable decode depth and encode max depth, parameter limit, and list index limit; optional strict-depth errors; duplicate-key strategies (combine/first/last).
28+
- Safety limits: configurable decode depth and encode max depth, parameter limit, and list element limit; optional strict-depth errors; duplicate-key strategies (combine/first/last).
2929
- Extras: numeric entity decoding (e.g. ``☺`` → ☺), alternate delimiters/regex, and query-prefix helpers.
3030

3131
Compatibility
@@ -497,12 +497,11 @@ Note that an empty ``str``\ing is also a value, and will be preserved:
497497
498498
assert qs.decode('a[0]=b&a[1]=&a[2]=c') == {'a': ['b', '', 'c']}
499499
500-
`decode <https://techouse.github.io/qs_codec/qs_codec.html#module-qs_codec.decode>`__ will also limit specifying indices
501-
in a ``list`` to a maximum index of ``20``. Any ``list`` members with an
502-
index of greater than ``20`` will instead be converted to a ``dict`` with
503-
the index as the key. This is needed to handle cases when someone sent,
504-
for example, ``a[999999999]`` and it will take significant time to iterate
505-
over this huge ``list``.
500+
`decode <https://techouse.github.io/qs_codec/qs_codec.html#module-qs_codec.decode>`__ also limits each ``list`` to a
501+
maximum element count of ``20``. Index ``19`` is the last index that can create
502+
a default ``list``; index ``20`` and higher are converted to a ``dict`` with
503+
the index as the key. This prevents inputs such as ``a[999999999]`` from
504+
creating massive sparse lists.
506505

507506
.. code:: python
508507
@@ -522,6 +521,24 @@ option:
522521
qs.DecodeOptions(list_limit=0),
523522
) == {'a': {'1': 'b'}}
524523
524+
The same limit is enforced cumulatively when duplicate keys, mixed list
525+
notation, or comma-separated values grow a list. A result exactly at the limit
526+
remains a ``list``. Above the limit, decoding uses a numeric-keyed ``dict`` by
527+
default, or raises ``ValueError`` when ``raise_on_limit_exceeded=True``.
528+
529+
.. code:: python
530+
531+
import qs_codec as qs
532+
533+
assert qs.decode(
534+
'a=x&a=y',
535+
qs.DecodeOptions(list_limit=1),
536+
) == {'a': {'0': 'x', '1': 'y'}}
537+
538+
With ``comma=True``, a flat comma value is subject to the same limit. A value
539+
assigned through ``[]=`` counts as one outer list element, so its inner
540+
comma-separated group may contain more values than ``list_limit``.
541+
525542
To disable ``list`` parsing entirely, set `parse_lists <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.models.decode_options.DecodeOptions.parse_lists>`__
526543
to ``False``.
527544

docs/README.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,25 @@ option:
427427
qs.DecodeOptions(list_limit=0),
428428
) == {'a': {'1': 'b'}}
429429
430+
The same limit is enforced cumulatively when duplicate keys, mixed list
431+
notation, or comma-separated values grow a list. A result exactly at the limit
432+
remains a ``list``. Above the limit, decoding uses a numeric-keyed ``dict`` by
433+
default, or raises ``ValueError`` when
434+
:py:attr:`raise_on_limit_exceeded <qs_codec.models.decode_options.DecodeOptions.raise_on_limit_exceeded>` is ``True``.
435+
436+
.. code:: python
437+
438+
import qs_codec as qs
439+
440+
assert qs.decode(
441+
'a=x&a=y',
442+
qs.DecodeOptions(list_limit=1),
443+
) == {'a': {'0': 'x', '1': 'y'}}
444+
445+
With ``comma=True``, a flat comma value is subject to the same limit. A value
446+
assigned through ``[]=`` counts as one outer list element, so its inner
447+
comma-separated group may contain more values than ``list_limit``.
448+
430449
To disable ``list`` parsing entirely, set :py:attr:`parse_lists <qs_codec.models.decode_options.DecodeOptions.parse_lists>`
431450
to ``False``.
432451

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Highlights
2929
- Pluggable hooks: custom ``encoder``/``decoder`` callables; options to sort keys, filter output, and control percent-encoding (keys-only, values-only).
3030
- Nulls & empties: ``strict_null_handling`` and ``skip_nulls``; support for empty lists/arrays when desired.
3131
- Dates: ``serialize_date`` for ISO 8601 or custom (e.g., UNIX timestamp).
32-
- Safety limits: configurable decode depth and encode max depth, parameter limit, and list index limit; optional strict-depth errors; duplicate-key strategies (combine/first/last).
32+
- Safety limits: configurable decode depth and encode max depth, parameter limit, and list element limit; optional strict-depth errors; duplicate-key strategies (combine/first/last).
3333
- Extras: numeric entity decoding (e.g. ``&#9786;`` → ☺), alternate delimiters/regex, and query-prefix helpers.
3434

3535
Compatibility

skills/qs-codec/SKILL.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,11 @@ Use these options with `qs.decode(query, qs.DecodeOptions(...))`:
184184
list; use `qs.Duplicates.FIRST` or `qs.Duplicates.LAST` to collapse.
185185
- Bracket lists: enabled by default; set `parse_lists=False` to treat list
186186
syntax as dictionary keys.
187-
- Large or sparse list indices: default `list_limit` is `20`; indices above the
188-
limit become dictionary keys.
187+
- List limits: default `list_limit` is `20`; numeric indices at or above the
188+
limit become dictionary keys. The limit also applies cumulatively to lists
189+
grown by duplicate keys, mixed notation, or comma-separated values. Exact-limit
190+
results remain lists; soft overflow becomes a numeric-keyed dictionary, while
191+
`raise_on_limit_exceeded=True` raises `ValueError`.
189192
- Comma-separated values such as `a=b,c`: `comma=True`.
190193
- Tokens without `=` as `None`: `strict_null_handling=True`.
191194
- Custom delimiters: `delimiter=";"` or `delimiter=re.compile(r"[;,]")`.
@@ -279,7 +282,9 @@ Warn or adjust before giving code for these cases:
279282
silently truncating.
280283
- `list_limit` has nuanced list-construction behavior; negative values disable
281284
numeric-index list parsing, and `raise_on_limit_exceeded=True` turns list
282-
limit violations into `ValueError`.
285+
limit violations into `ValueError`. With `comma=True`, a flat comma value is
286+
checked before value decoding, while a comma group assigned through `[]=`
287+
counts as one outer list element.
283288
- Built-in charset handling supports only `qs.Charset.UTF8` and
284289
`qs.Charset.LATIN1`; other encodings require a custom `encoder` or `decoder`.
285290
- `EncodeOptions.encoder` is ignored when `encode=False`.

src/qs_codec/models/decode_options.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ class DecodeOptions:
4747
limit, index ``19`` is the last index that can create a list; index ``20`` already
4848
overflows to a ``dict``.
4949
50-
This limit also applies to decoded list growth from comma-split values when ``comma=True``.
50+
This limit also applies cumulatively to list growth from duplicate keys, mixed list
51+
notation, and comma-split values when ``comma=True``. A result exactly at the limit remains
52+
a list. Above the limit, decoding either uses a numeric-keyed mapping or raises ``ValueError``
53+
when ``raise_on_limit_exceeded=True``.
54+
5155
For bracket-array assignments such as ``foo[]=1,2,3``, the comma-split payload is wrapped
5256
as a single outer list element, so the inner payload may contain more values than
5357
``list_limit`` while still respecting the outer container limit.
@@ -122,11 +126,11 @@ class DecodeOptions:
122126
"""Raise instead of degrading gracefully when limits are exceeded.
123127
124128
When ``True``, the decoder raises:
125-
- a ``DecodeError`` for parameter and list limit violations; and
129+
- a ``ValueError`` for parameter and list limit violations; and
126130
- an ``IndexError`` when nesting deeper than ``depth`` **and** ``strict_depth=True``.
127131
128132
When ``False`` (default), the decoder degrades gracefully: it slices the parameter list
129-
at ``parameter_limit``, stops adding items beyond ``list_limit``, and—if
133+
at ``parameter_limit``, represents list overflows as numeric-keyed mappings, and—if
130134
``strict_depth=True``—stops descending once ``depth`` is reached without raising.
131135
"""
132136

0 commit comments

Comments
 (0)