Skip to content

Conversation

@techouse
Copy link
Owner

@techouse techouse commented Jan 11, 2026

This pull request introduces a new mechanism to prevent denial-of-service (DoS) attacks caused by excessive list growth during query string decoding. It does this by converting lists that exceed a configurable list_limit into a special OverflowDict object, which uses numeric keys instead of a Python list. This approach ensures memory safety and aligns behavior with the latest qs JavaScript library. The changes also include comprehensive updates to the decoding logic, utility functions, and tests to support and verify this overflow handling.

Overflow handling and DoS protection:

  • Introduced a new OverflowDict class in utils/utils.py to represent lists that exceed the configured list_limit, preventing unbounded memory usage during query string decoding.
  • Updated the Utils.combine method to convert combined lists into an OverflowDict when the length exceeds the limit, and added logic to handle merging and appending to OverflowDict instances.
  • Modified decoding routines in decode.py to use OverflowDict when limits are exceeded, and to propagate overflow objects through recursive parsing and merging. [1] [2] [3] [4] [5]

API and utility updates:

  • Added OverflowDict to the public API in __init__.py and updated all relevant imports and usages throughout the codebase. [1] [2] [3] [4] [5]

Testing and security validation:

  • Enhanced and added new unit tests to verify correct behavior when list limits are exceeded, including tests for edge cases (zero and negative limits), merging behavior, and regression tests for CVE-2024 DoS scenarios. [1] [2] [3] [4] [5] [6]

Dependency update:

  • Updated the qs dependency in the test comparison suite to version 6.14.1 to ensure parity with the latest security and behavior fixes.

Relates to GHSA-6rw7-vpxm-498p

Summary by CodeRabbit

  • New Features
    • Added overflow detection for query string arrays; arrays exceeding configurable size limits now convert to dictionary-like structures with numeric string keys rather than remaining as traditional lists.
  • Tests
    • Expanded test coverage for array limit enforcement, overflow scenarios, and associated edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@techouse techouse self-assigned this Jan 11, 2026
@techouse techouse added the bug Something isn't working label Jan 11, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

Warning

Rate limit exceeded

@techouse has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e7d2461 and 1d6e43c.

📒 Files selected for processing (2)
  • src/qs_codec/models/overflow_dict.py
  • tests/unit/utils_test.py
📝 Walkthrough

Walkthrough

This PR introduces list overflow handling to prevent denial-of-service attacks via deeply nested query string arrays. A new OverflowDict class is added as a typed marker for overflow conditions. The Utils.combine() method is enhanced with overflow-aware merging logic, and a new Utils.is_overflow() utility function is provided. Decode logic is updated to check for and wrap oversized lists using OverflowDict with numeric string keys instead of standard arrays when limits are exceeded.

Changes

Cohort / File(s) Summary
Core Overflow Mechanism
src/qs_codec/models/overflow_dict.py
New public class OverflowDict (dict subclass) with custom copy() and \\__copy() methods that preserve type identity. Serves as a marker for list overflow conditions.
Utilities Enhancement
src/qs_codec/utils/utils.py
Added helper _numeric_key_pairs() and public is_overflow() detection function. Significantly expanded Utils.combine(a, b, options) with overflow-aware merge logic: detects OverflowDict inputs, converts to numeric-indexed structures, applies list limits, and returns OverflowDict when overflow occurs or when merging with OverflowDict sources.
Decode Logic
src/qs_codec/decode.py
Imported OverflowDict and updated parsing paths to call Utils.is_overflow() on leaves before converting to list/dict structures. Routes overflow scenarios to wrap values in OverflowDict; adjusted Utils.combine() calls to pass options parameter.
Test Coverage Updates
tests/unit/decode_test.py, tests/unit/utils_test.py
Updated expectations for list_limit=0 scenarios to yield dict-like structures with string indices instead of arrays. New comprehensive test suite for Utils.combine() overflow behavior, OverflowDict merging, and edge cases with Undefined values. Tests now validate overflow behavior at various list limits (-1, 0, 1, 2, no limit).
Dependencies
tests/comparison/package.json
Minor version bump: qs dependency updated from ^6.14.0 to ^6.14.1.

Sequence Diagram(s)

sequenceDiagram
    participant Decoder as Decoder Logic
    participant Utils as Utils
    participant OverflowDict as OverflowDict

    Decoder->>Decoder: Parse query string values
    Decoder->>Utils: is_overflow(leaf)?
    Utils-->>Decoder: true/false
    
    alt Overflow Detected or List Limit Hit
        Decoder->>Utils: combine(existing, new_val, options)
        Utils->>Utils: Check if inputs are OverflowDict
        Utils->>Utils: Merge/flatten with numeric indices
        Utils->>Utils: Compare length vs list_limit
        alt Exceeds Limit
            Utils->>OverflowDict: Create with string keys
            OverflowDict-->>Utils: OverflowDict instance
        else Within Limit
            Utils->>Utils: Return as list or dict
        end
        Utils-->>Decoder: Result (OverflowDict or list/dict)
    else Normal Path
        Decoder->>Utils: combine(existing, new_val, options)
        Utils-->>Decoder: Merged result
    end
    
    Decoder->>Decoder: Finalize parse result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Long arrays once unbounded grew,
Now OverflowDict tames the queue,
With numeric keys so neat and tight,
We parse the strings—DOS attacks take flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: implementing list_limit handling in Utils.combine to prevent DoS attacks via memory exhaustion.
Description check ✅ Passed The description provides comprehensive details about the changes, addresses a security concern (DoS prevention), includes links to relevant issues, and covers testing updates. The template checklist sections are appropriately addressed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link

codacy-production bot commented Jan 11, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.00% (target: -1.00%) 100.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (a5cb011) 1162 1162 100.00%
Head commit (1d6e43c) 1261 (+99) 1261 (+99) 100.00% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#37) 105 105 100.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

@codecov
Copy link

codecov bot commented Jan 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (a5cb011) to head (1d6e43c).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main       #37   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           16        17    +1     
  Lines         1162      1261   +99     
=========================================
+ Hits          1162      1261   +99     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements list limit handling in the query string decoder to prevent denial-of-service (DoS) attacks via memory exhaustion. When decoded lists exceed the configured list_limit, they are converted to a special OverflowDict type (a dictionary with numeric string keys) rather than continuing to grow as lists.

Changes:

  • Introduced OverflowDict class to represent lists that exceeded the limit
  • Modified Utils.combine to enforce list limits and handle OverflowDict instances
  • Updated Utils.merge to detect and handle OverflowDict instances
  • Enhanced decoding logic in decode.py to check for overflow conditions
  • Added comprehensive tests including DoS attack simulation and list limit edge cases

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/qs_codec/utils/utils.py Adds OverflowDict class, updates combine to enforce limits and return dicts when exceeded, adds overflow detection in merge
src/qs_codec/decode.py Imports OverflowDict, passes options to combine, checks for overflow in _parse_object
tests/unit/decode_test.py Updates expected results for list_limit=0 cases, adds TestCVE2024 class with DoS attack tests
Comments suppressed due to low confidence (1)

src/qs_codec/utils/utils.py:191

  • When merging a non-mapping target with an OverflowDict source, the function extracts values and creates a list _res without checking if the resulting list exceeds list_limit. This could bypass the DoS protection. After creating _res, check if options has a list_limit and convert to OverflowDict if exceeded, similar to the logic in the combine function.
            _res: t.List[t.Any] = []
            _iter1 = target if isinstance(target, (list, tuple)) else [target]
            for _el in _iter1:
                if not isinstance(_el, Undefined):
                    _res.append(_el)
            _iter2 = (
                source
                if isinstance(source, (list, tuple))
                else (list(source.values()) if Utils.is_overflow(source) else [source])
            )
            for _el in _iter2:
                if not isinstance(_el, Undefined):
                    _res.append(_el)
            return _res

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/qs_codec/utils/utils.py (1)

345-383: Well-implemented overflow handling in combine.

The implementation correctly:

  1. Appends to existing OverflowDict at sequential indices
  2. Handles all source types (list, tuple, OverflowDict, scalar)
  3. Defaults to list_limit=20 when options is None
  4. Converts result to OverflowDict only when len(res) > list_limit

One minor note: the static analysis flagged line 352 for using a non-breaking hyphen () in the docstring instead of a regular hyphen (-). This is a cosmetic issue.

🔧 Optional: Fix non-breaking hyphen in docstring
         """
         Concatenate two values, treating non‑sequences as singletons.

-        If `list_limit` is exceeded, converts the list to an `OverflowDict`
-        (a dict with numeric keys) to prevent memory exhaustion.
+        If `list_limit` is exceeded, converts the list to an `OverflowDict`
+        (a dict with numeric keys) to prevent memory exhaustion.
         """

The word "non‑sequences" uses a non-breaking hyphen. Consider using a regular hyphen for consistency.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5cb011 and 629fdb8.

📒 Files selected for processing (4)
  • src/qs_codec/decode.py
  • src/qs_codec/utils/utils.py
  • tests/comparison/package.json
  • tests/unit/decode_test.py
🧰 Additional context used
📓 Path-based instructions (7)
src/qs_codec/{encode.py,decode.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/qs_codec/{encode.py,decode.py}: Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)
Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Implement dot handling: allow_dots toggles dotted path parsing; encode_dot_in_keys/decode_dot_in_keys must percent-encode/decode literal dots with correct order and validation
Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted
Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility
Avoid repeated regex compilation and deep copies in tight loops; reuse preprocessing stages (tokenize first, structure later)

Files:

  • src/qs_codec/decode.py
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/decode.py
  • src/qs_codec/utils/utils.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
src/qs_codec/decode.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Treat option dataclasses as immutable; when toggling transient flags in decode.decode, clone with dataclasses.replace

Files:

  • src/qs_codec/decode.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/decode_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/decode_test.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/decode.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/decode.py : Treat option dataclasses as immutable; when toggling transient flags in decode.decode, clone with dataclasses.replace

Applied to files:

  • src/qs_codec/decode.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Avoid repeated regex compilation and deep copies in tight loops; reuse preprocessing stages (tokenize first, structure later)

Applied to files:

  • src/qs_codec/decode.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode_options.py,decode_options.py} : Extend option behavior by updating the corresponding dataclass with defaults and type hints (encode_options.py or decode_options.py)

Applied to files:

  • src/qs_codec/decode.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • src/qs_codec/decode.py
  • tests/unit/decode_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/comparison/test_cases.json : When adding features, update cross-language comparison fixtures (test_cases.json) to maintain symmetry with Node qs

Applied to files:

  • tests/comparison/package.json
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {docs/**,README.md} : Document intentional divergences from npm qs in README and docs, and update docs when public behavior or options change

Applied to files:

  • tests/comparison/package.json
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • tests/unit/decode_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {src/qs_codec/enums/**/*.py,tests/unit/**/*.py} : For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Applied to files:

  • tests/unit/decode_test.py
🧬 Code graph analysis (3)
src/qs_codec/decode.py (1)
src/qs_codec/utils/utils.py (4)
  • OverflowDict (31-34)
  • Utils (37-437)
  • combine (346-383)
  • is_overflow (341-343)
tests/unit/decode_test.py (2)
src/qs_codec/utils/utils.py (1)
  • OverflowDict (31-34)
src/qs_codec/models/decode_options.py (1)
  • decode (267-281)
src/qs_codec/utils/utils.py (1)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
🪛 Ruff (0.14.10)
src/qs_codec/utils/utils.py

352-352: Docstring contains ambiguous (NON-BREAKING HYPHEN). Did you mean - (HYPHEN-MINUS)?

(RUF002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (14)
tests/comparison/package.json (1)

8-8: Version bump aligns with security fix.

The qs dependency update to ^6.14.1 maintains parity with the upstream Node.js library's fix for the array limit DoS vulnerability (GHSA-6rw7-vpxm-498p). This ensures the Python port's behavior matches the reference implementation.

tests/unit/decode_test.py (5)

13-13: LGTM!

Import correctly added for the new OverflowDict type used in test assertions.


321-326: LGTM!

Test expectation correctly updated to reflect that list_limit=0 now produces a dict-like structure with numeric string keys instead of a list.


578-594: LGTM!

Test expectations correctly updated for list_limit=0 with strict_null_handling. The dict output preserves None values appropriately.


1335-1337: LGTM!

Explicit test case for list_limit=0 behavior added, verifying conversion to dict with numeric string keys.


1689-1731: Good regression test coverage for the security vulnerability.

The TestCVE2024 class provides comprehensive coverage:

  • test_dos_attack: Simulates the attack vector with 105 elements and list_limit=100, verifying conversion to OverflowDict
  • test_array_limit_checks: Mirrors the JS patch tests, verifying edge cases at limits 0, 1, and 2
  • test_no_limit_does_not_overflow: Confirms lists stay as lists when within the limit

The assertions correctly verify both the dict type and the specific OverflowDict subclass. Based on learnings, these regression tests align with the requirement to add tests for limits.

src/qs_codec/decode.py (4)

30-30: LGTM!

Import correctly added for OverflowDict alongside the existing Utils import.


291-291: LGTM!

Correctly passes options to Utils.combine to enable list_limit enforcement during duplicate key combination. This aligns with the coding guideline to honor Duplicates policy during merge.


363-371: LGTM!

The overflow handling in _parse_object correctly:

  1. Detects already-overflowed leaves via Utils.is_overflow() to avoid re-wrapping
  2. Converts newly created lists to OverflowDict when they exceed list_limit

Per coding guidelines, this enforces list_limit exactly as specified.


395-399: LGTM!

Correctly handles the case where parse_lists=False and an empty bracket segment is encountered. If the leaf is already an OverflowDict, it's used directly; otherwise, the standard {"0": leaf} coercion applies.

src/qs_codec/utils/utils.py (4)

31-35: LGTM!

Clean implementation of OverflowDict as a marker subclass. The docstring clearly describes its purpose.


151-154: LGTM!

Correctly routes OverflowDict targets through Utils.combine for proper overflow-aware appending, maintaining the marker type through subsequent merge operations. This adheres to the coding guideline of adjusting merge logic within Utils.merge.


183-187: LGTM!

Correctly handles OverflowDict sources by extracting values via list(source.values()) for iteration, ensuring overflow contents are properly merged.


340-343: LGTM!

Simple and effective utility method for detecting OverflowDict instances.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/qs_codec/__init__.py (1)

30-50: Remove OverflowDict from __all__ or update version and CHANGELOG.

OverflowDict is currently exported in __all__ but is not part of the specified public API surface (per learnings: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined). Either remove OverflowDict from __all__ since it's an internal utility, or if this public export is intentional, bump __version__ from "1.3.2" and add a CHANGELOG entry documenting this new public symbol.

🤖 Fix all issues with AI agents
In @src/qs_codec/utils/utils.py:
- Around line 346-395: The combine() function mishandles OverflowDicts: (1) when
appending to an existing OverflowDict (Utils.is_overflow(a)), compute the next
numeric index by finding the maximum numeric key + 1 (parsing keys as ints and
handling sparse keys) rather than using len(a_copy); (2) when b is an
OverflowDict and a is not, iterate b.values() (or ordered items) and append each
element instead of treating b as a single element so items are merged/flattened
in order; keep the branch that handles b as list/tuple and the scalar case;
preserve ordering when merging two OverflowDicts by iterating keys in numeric
order; also replace non‑breaking hyphens in the combine() docstring with normal
hyphens to satisfy Ruff.
🧹 Nitpick comments (3)
tests/unit/utils_test.py (1)

632-671: Add regression coverage for sparse OverflowDict keys (realistic DoS vector).
Current tests only cover sequential "0".."n" keys. Please add at least one test like OverflowDict({"999": "x"}) and verify combine()/merge() append at "1000" (not "1") and that ordering is deterministic. This will catch subtle index/ordering bugs early.
Based on learnings for tests/unit/**/*.py limit regressions.

src/qs_codec/utils/utils.py (2)

31-35: OverflowDict type is OK, but consider tightening the docstring/typing.
The one-line docstring is a bit long; optionally wrap it for readability / line-length consistency.


183-187: Dead/odd branch: isinstance(source, (list, tuple)) can’t be true here.
This code path is under “source is a mapping”, so the list/tuple check is unreachable. Consider simplifying _iter2 to only distinguish OverflowDict vs normal mapping (and be explicit about ordering expectations).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 629fdb8 and 7176a1c.

📒 Files selected for processing (3)
  • src/qs_codec/__init__.py
  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (6)
src/qs_codec/__init__.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Expose the public API only via all in qs_codec/init.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Files:

  • src/qs_codec/__init__.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/__init__.py
  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/utils_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/utils_test.py
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • src/qs_codec/__init__.py
  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/__init__.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • src/qs_codec/__init__.py
  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/__init__.py
  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/__init__.py
  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/__init__.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode_options.py,decode_options.py} : Extend option behavior by updating the corresponding dataclass with defaults and type hints (encode_options.py or decode_options.py)

Applied to files:

  • src/qs_codec/__init__.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {src/qs_codec/enums/**/*.py,tests/unit/**/*.py} : For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/utils_test.py
🧬 Code graph analysis (2)
src/qs_codec/__init__.py (1)
src/qs_codec/utils/utils.py (1)
  • OverflowDict (31-34)
src/qs_codec/utils/utils.py (1)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
🪛 Ruff (0.14.10)
src/qs_codec/utils/utils.py

352-352: Docstring contains ambiguous (NON-BREAKING HYPHEN). Did you mean - (HYPHEN-MINUS)?

(RUF002)


361-361: Docstring contains ambiguous (NON-BREAKING HYPHEN). Did you mean - (HYPHEN-MINUS)?

(RUF002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (4)
tests/unit/utils_test.py (2)

3-14: Import updates look fine and keep tests focused.
patch + OverflowDict imports are appropriate for the new delegation/overflow assertions.


896-904: This test is correct and the behavior is intentional. When merging a mapping (including OverflowDict) into a list, the merge function coerces the list to an index-keyed dict and then overlays the source mapping. The docstring at lines 65-68 explicitly states this rule: "if target is a sequence, coerce it to an index‑keyed dict and merge." The spread operator implementation ({...__coerced_target__, **source} at lines 170-173) correctly places source last, allowing it to overwrite colliding keys. This behavior is qs-compatible for query string parsing where array indices naturally become dict keys during structural reconstruction.

src/qs_codec/utils/utils.py (2)

151-154: Good: merge delegates overflow-target handling to combine (centralizes logic).
This keeps overflow merge behavior in one place, aligning with the “don’t inline merge logic elsewhere” guideline.


340-344: Utils.is_overflow() looks good and keeps the type-check centralized.

@techouse techouse changed the title 🐛 implement list limit handling in combine function to prevent DoS via memory exhaustion 🐛 implement DecodeOptions. list_limit handling in combine function to prevent DoS via memory exhaustion Jan 11, 2026
@techouse techouse changed the title 🐛 implement DecodeOptions. list_limit handling in combine function to prevent DoS via memory exhaustion 🐛 implement DecodeOptions.list_limit handling in combine function to prevent DoS via memory exhaustion Jan 11, 2026
@techouse techouse changed the title 🐛 implement DecodeOptions.list_limit handling in combine function to prevent DoS via memory exhaustion 🐛 implement DecodeOptions.list_limit handling in Utils.combine function to prevent DoS via memory exhaustion Jan 11, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @tests/unit/utils_test.py:
- Line 3: The file imports unittest.mock.patch but it is unused (Flake8 F401);
remove the unused import from tests/unit/utils_test.py by deleting the "patch"
import entry (i.e., remove or replace the "from unittest.mock import patch"
line) and run the tests/linter to confirm no remaining references to the patch
symbol.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7176a1c and 5658b19.

📒 Files selected for processing (2)
  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (5)
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/utils_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/utils_test.py
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {src/qs_codec/enums/**/*.py,tests/unit/**/*.py} : For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/utils/utils.py
🧬 Code graph analysis (1)
src/qs_codec/utils/utils.py (1)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
🪛 Flake8 (7.3.0)
tests/unit/utils_test.py

[error] 3-3: 'unittest.mock.patch' imported but unused

(F401)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
🔇 Additional comments (6)
tests/unit/utils_test.py (3)

13-13: OverflowDict import in tests is fine.


632-672: Good coverage for list_limit overflow + OverflowDict immutability/append semantics.

One gap worth adding: a regression asserting Undefined() elements are skipped when combining/merging involving OverflowDict (otherwise sentinels can leak into intermediate structures). Based on learnings, this is especially relevant for limits/merge behavior in tests/unit/**/*.py.


881-956: Nice edge-case coverage (sparse keys, numeric ordering, merge delegation).

Two small nits:

  • Comments referencing specific utils.py line numbers (e.g., “lines 403-404”) will go stale quickly; consider describing behavior instead.
  • Consider adding one test for non-numeric keys in an OverflowDict to lock in desired behavior (either robust fallback or explicit failure), since OverflowDict is a public type at module scope.
src/qs_codec/utils/utils.py (3)

31-35: OverflowDict marker type is a clean way to distinguish overflowed lists.


183-188: Numeric-key-order iteration for OverflowDict source preserves list semantics.


341-345: Utils.is_overflow() helper is straightforward and keeps call sites readable.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/unit/utils_test.py (1)

631-997: Consider adding edge case tests for list_limit=0 and negative limits.

The new test coverage is comprehensive for the core OverflowDict functionality. Based on the PR objectives mentioning handling of list_limit=0, consider adding explicit tests here if not already covered in the TestCVE2024 class:

def test_combine_list_limit_zero_creates_overflow_dict(self) -> None:
    """When list_limit=0, any non-empty result should overflow."""
    options = DecodeOptions(list_limit=0)
    combined = Utils.combine(["a"], [], options)
    assert isinstance(combined, OverflowDict)
    assert combined == {"0": "a"}

As per coding guidelines, regression tests for limits help ensure robustness against DoS vectors.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5658b19 and d32a24b.

📒 Files selected for processing (2)
  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (5)
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/utils_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/utils_test.py
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {src/qs_codec/enums/**/*.py,tests/unit/**/*.py} : For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • tests/unit/utils_test.py
🧬 Code graph analysis (2)
src/qs_codec/utils/utils.py (2)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
src/qs_codec/models/undefined.py (1)
  • Undefined (17-69)
tests/unit/utils_test.py (2)
src/qs_codec/utils/utils.py (4)
  • OverflowDict (31-34)
  • Utils (37-491)
  • combine (361-437)
  • merge (46-206)
src/qs_codec/models/undefined.py (1)
  • Undefined (17-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (13)
tests/unit/utils_test.py (8)

12-12: LGTM!

The import correctly brings in OverflowDict alongside Utils from the utils module.


631-639: LGTM!

Good boundary test validating that exceeding the default list_limit (20) triggers OverflowDict creation, with correct assertions on type, length, and indexed values.


641-653: LGTM!

Excellent test validating immutability of the original OverflowDict during combine operations, with comprehensive assertions on copy semantics and correct index assignment.


655-670: LGTM!

Good coverage of default options behavior and OverflowDict-to-OverflowDict combination with correct key assignment semantics.


880-934: LGTM!

Comprehensive tests for merge with OverflowDict covering:

  • Target as OverflowDict delegating to combine
  • Source as OverflowDict into regular dict/list
  • Sparse key handling with numeric sorting verification

The reverse-order key insertion on lines 929-930 is a smart test pattern for verifying numeric sorting.


904-954: LGTM!

Excellent edge case coverage for sparse OverflowDict handling. The test at line 911 correctly validates the max-key-plus-one behavior ("1000" for sparse key "999"), ensuring the implementation doesn't incorrectly use len() for index calculation.


956-983: LGTM!

Thorough coverage of Undefined handling in combine operations. The test at lines 975-983 is particularly valuable, verifying that:

  • Non-numeric keys ("skip") are preserved but ignored for index calculation
  • Existing Undefined values are filtered out during copy
  • New values are correctly assigned to the next numeric index

As per coding guidelines, these tests help ensure limits are enforced correctly.


985-997: LGTM!

Good tests verifying that non-numeric keys in OverflowDict sources are correctly excluded during combine and merge operations, with numeric keys processed in sorted order.

src/qs_codec/utils/utils.py (5)

31-35: LGTM!

Clean marker class design using dict subclass. This is an appropriate pattern for tagging overflow-converted structures without adding behavioral overhead.


152-154: LGTM!

Correct delegation to combine when target is an OverflowDict, maintaining overflow semantics and adhering to the guideline of keeping merge logic centralized.


183-194: LGTM!

Correct handling of OverflowDict sources with numeric key ordering to preserve list semantics during merge. The implementation properly filters non-numeric keys and skips Undefined values.


343-358: LGTM!

Clean helper implementations. The _numeric_key_pairs method correctly handles edge cases with try/except for non-coercible keys.

One minor note: negative numeric keys (e.g., "-1") would be accepted and sorted before positive indices. This is likely fine since QS inputs shouldn't produce negative indices, but worth being aware of if this assumption changes.


360-437: LGTM!

Well-structured rewrite of combine with comprehensive overflow handling:

  • Correct max-key-plus-one indexing for sparse OverflowDicts (line 390)
  • Proper immutability via copy-on-write (lines 387-388)
  • Consistent Undefined filtering across all code paths
  • Clear docstring documenting the behavior including edge cases (negative limits)

The logic correctly allows exactly list_limit items (using > comparison at line 433), converting only when exceeded.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/unit/decode_test.py (1)

322-336: Consider asserting OverflowDict type for list_limit=0 cases.

The test expectations verify the structure but do not assert that result["a"] is an OverflowDict instance. For consistency with the CVE2024 tests (lines 1713-1714) that explicitly check isinstance(result["a"], OverflowDict), consider adding type assertions here.

♻️ Proposed enhancement
             pytest.param(
                 "a[]=b&a=c",
                 DecodeOptions(list_limit=0),
                 {"a": {"0": "b", "1": "c"}},
                 id="explicit-list-with-zero-limit",
             ),
+            # ... in the test method body:
+            assert isinstance(result["a"], OverflowDict), "Expected OverflowDict when list_limit=0"
src/qs_codec/utils/utils.py (1)

178-190: Consider simplifying the overflow value extraction.

The current implementation extracts overflow_values separately (line 181) and then zips it with sorted_pairs (line 187). This can be simplified by directly iterating over sorted pairs and accessing values.

♻️ Proposed simplification
             if Utils.is_overflow(source):
                 source_of = t.cast(OverflowDict, source)
                 sorted_pairs = sorted(Utils._numeric_key_pairs(source_of), key=lambda item: item[0])
-                overflow_values = [source_of[k] for _, k in sorted_pairs]
                 result = OverflowDict()
                 offset = 0
                 if not isinstance(target, Undefined):
                     result["0"] = target
                     offset = 1
-                for (numeric_key, _), val in zip(sorted_pairs, overflow_values):
+                for numeric_key, original_key in sorted_pairs:
+                    val = source_of[original_key]
                     if not isinstance(val, Undefined):
                         result[str(numeric_key + offset)] = val
                 return result
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da9341d and 25c4055.

📒 Files selected for processing (3)
  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (5)
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • tests/unit/decode_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {src/qs_codec/enums/**/*.py,tests/unit/**/*.py} : For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Applied to files:

  • tests/unit/utils_test.py
🧬 Code graph analysis (2)
src/qs_codec/utils/utils.py (2)
src/qs_codec/models/undefined.py (1)
  • Undefined (17-69)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
tests/unit/decode_test.py (3)
src/qs_codec/utils/utils.py (1)
  • OverflowDict (31-34)
src/qs_codec/models/decode_options.py (2)
  • DecodeOptions (20-293)
  • decode (267-281)
src/qs_codec/decode.py (1)
  • decode (33-103)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (7)
tests/unit/decode_test.py (1)

1694-1735: LGTM! Comprehensive DoS protection tests.

The CVE2024 test class effectively validates the overflow behavior:

  • Verifies that exceeding list_limit produces OverflowDict with correct numeric string keys
  • Tests boundary conditions (0, 1, 2, -1 limits)
  • Confirms that staying within limits preserves list semantics

The logic correctly implements len(res) > list_limit for overflow detection.

src/qs_codec/utils/utils.py (4)

31-34: LGTM! Clean marker class for overflow detection.

The OverflowDict subclass provides a simple, type-safe way to distinguish overflowed containers from regular dicts. The minimal implementation (no custom methods) is appropriate for a marker class.


152-153: LGTM! Correct delegation to combine for overflow targets.

When the target is an OverflowDict and source is a non-mapping, delegating to combine ensures consistent overflow handling and proper index calculation.


350-365: LGTM! Clean utility methods for overflow handling.

Both helpers are well-designed:

  • is_overflow provides a clear type-checking interface
  • _numeric_key_pairs robustly extracts numeric keys while gracefully handling non-numeric keys

The error handling in _numeric_key_pairs correctly catches both TypeError (for non-stringifiable keys) and ValueError (for non-numeric strings).


390-444: LGTM! Robust overflow handling with comprehensive edge case coverage.

The combine implementation correctly handles:

  • Overflow appending to existing OverflowDict using max numeric key + 1
  • Sorting numeric keys when merging overflow dicts
  • Preserving non-numeric keys in overflow dicts
  • Filtering Undefined values throughout
  • Converting to OverflowDict when len(res) > list_limit (handles 0, negative, and positive limits correctly)

The logic for negative list_limit correctly overflows immediately since any non-empty result will have len(res) > -1.

tests/unit/utils_test.py (2)

631-683: LGTM! Comprehensive overflow behavior tests.

The test suite thoroughly covers:

  • Default limit (20) boundary conditions
  • Zero and negative limits
  • OverflowDict immutability during combine operations
  • Combining OverflowDict instances with each other and with scalars/lists

All test expectations align correctly with the implementation logic.


892-1012: LGTM! Excellent test coverage for merge/combine with OverflowDict.

The tests comprehensively verify:

  • Merging with OverflowDict as target or source
  • Sparse OverflowDict handling (numeric key ordering, max key + 1 insertion)
  • Non-numeric key preservation and exclusion from index calculations
  • Undefined filtering in all code paths
  • Immutability of input OverflowDicts

The sparse dict test (line 935-947) correctly validates that numeric keys are sorted and offset is applied, producing {"0": "a", "3": "b", "11": "c"} from target "a" and source with keys "2" and "10".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/qs_codec/utils/utils.py:
- Around line 209-221: The merge logic in Utils.merge uses the raw key
membership check but writes into the merged dict with str(key), causing missed
recursive merges when merge_target has non-string keys; update the membership
test to check for str(key) in merge_target (or normalize keys consistently) so
the conditional invokes Utils.merge for the same normalized key used when
assigning, ensuring recursive merges occur for existing entries in merge_target
(refer to Utils.merge, merge_target, and the comprehension producing merged).
🧹 Nitpick comments (4)
tests/unit/utils_test.py (1)

631-683: Avoid hard-coding the default list_limit in tests.

These tests assume the default list_limit is 20 (e.g., “default limit is 20”, “Default options should imply list_limit=20”). Consider deriving it from DecodeOptions() (or passing options=DecodeOptions()) so tests won’t break if defaults change for compatibility/security reasons.

src/qs_codec/utils/utils.py (3)

31-35: OverflowDict docstring is fine; consider clarifying mutability expectations.

Tests appear to rely on “copy-on-write” (combine returns a copy) rather than true immutability; consider adjusting wording/comments elsewhere to avoid implying OverflowDict itself is immutable.


178-195: Simplify scalar-target + OverflowDict-source merging and avoid double iteration.

Current logic builds overflow_values separately and zips it back with sorted_pairs; you can iterate sorted_pairs once and read source_of[k] directly. Minor, but reduces bookkeeping in a hot path.


357-373: Helpers look fine; watch out for duplicate numeric coercions.

int("01") and int("1") both coerce to 1; if both keys exist, later writes to str(numeric_key + offset) can overwrite earlier ones. If that’s acceptable (last-wins), it’d be worth documenting.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25c4055 and ab9adb4.

📒 Files selected for processing (2)
  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (5)
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/utils_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/utils_test.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • tests/unit/utils_test.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (4)
tests/unit/utils_test.py (2)

12-12: Import path may bypass intended public API surface.

If OverflowDict is meant to be part of the public API, consider importing it from qs_codec (package root) rather than qs_codec.utils.utils to ensure tests reflect the supported surface area. Based on learnings, ensure __all__ in qs_codec/__init__.py remains the single source of exported API.


892-1019: Nice coverage of overflow merge/combine edge cases.

Good coverage for: sparse numeric keys, skipping Undefined, preservation vs skipping of non-numeric keys (merge preserves, combine skips), and copy-on-write expectations when appending to an OverflowDict.

src/qs_codec/utils/utils.py (2)

151-154: OverflowDict target routing in merge() looks correct.

Early return to Utils.combine(target, source, options) for overflow targets is the right way to keep list semantics without re-materializing Python lists. Based on learnings, this keeps list/index logic centralized in Utils.*.


374-451: combine(): fix docstring claim about negative list_limit and update stale comment.

  1. Doc claims negative list_limit overflows "non-empty" results, but the code len(res) > -1 also triggers for empty lists—any combined result including empty ([]) gets converted. Add a test case for combining two empty lists with negative limit to confirm intended behavior.

  2. Line 399 comment states "len(a_copy) gives the next index," but the implementation uses max(key)+1 to safely handle sparse dicts. Update the comment to match the actual logic.

Regarding raise_on_limit_exceeded: the docstring correctly states this helper never raises and intentionally ignores the flag, converting to OverflowDict instead. This is consistent with the library's documented behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/qs_codec/utils/utils.py (2)

178-195: Complex but correct overflow merging logic.

The offset-based merging correctly handles the case where a scalar/sequence target is combined with an OverflowDict source. The logic:

  1. Extracts and sorts numeric keys from the source
  2. Reserves index "0" for the target (if not Undefined)
  3. Shifts source numeric indices by the offset
  4. Preserves non-numeric keys unchanged

This prevents index collisions and maintains list semantics.

However, consider adding an inline comment at line 189 explaining why the offset is calculated, as this logic is non-trivial:

# Offset ensures target occupies index "0"; source indices shift up by 1

209-224: Overflow status correctly preserved through merge.

Tracking is_overflow_target and wrapping the result in OverflowDict when appropriate ensures that overflow protection persists through recursive merges, maintaining DoS prevention guarantees.

Minor performance note: Line 210's copy.deepcopy(target) could be expensive for large nested structures. Since the code immediately overwrites values during merge, consider whether a shallow copy with selective deep-copying of mutable values would suffice—though this optimization may be premature without profiling.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab9adb4 and ceef2ee.

📒 Files selected for processing (1)
  • src/qs_codec/utils/utils.py
🧰 Additional context used
📓 Path-based instructions (3)
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/utils/utils.py
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/utils/utils.py
🧬 Code graph analysis (1)
src/qs_codec/utils/utils.py (2)
src/qs_codec/models/undefined.py (1)
  • Undefined (17-69)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (5)
src/qs_codec/utils/utils.py (5)

31-34: LGTM - marker class design is appropriate.

The OverflowDict marker pattern is clean and allows the library to distinguish overflow-converted dicts from normal dicts without enforcing immutability at the class level. This provides flexibility while the library maintains immutability guarantees through careful handling.


152-153: Overflow routing preserves list_limit semantics.

Correctly delegates to Utils.combine when the target is an OverflowDict, ensuring consistent overflow handling and list_limit enforcement.


361-376: Clean helper utilities for overflow detection and key extraction.

Both is_overflow and _numeric_key_pairs are well-implemented:

  • is_overflow provides a clear public API for overflow detection
  • _numeric_key_pairs safely extracts numeric keys with proper exception handling

379-427: Robust overflow handling when a is OverflowDict.

The logic correctly:

  • Preserves the OverflowDict type when a is already overflowed
  • Calculates the next index using max(numeric_keys) + 1 to handle sparse dicts safely
  • Handles different types of b (list, tuple, OverflowDict, scalar) with appropriate flattening
  • Filters Undefined sentinels throughout

The use of max(key for key, _ in key_pairs) + 1 (line 408) correctly handles sparse overflow dicts like {"0": "a", "5": "b"} by appending at index 6, preserving the sparse structure intentionally created by overflow conversion.


428-455: List flattening and overflow conversion work correctly.

The normal combination path properly:

  • Flattens lists/tuples and scalars while filtering Undefined
  • Handles OverflowDict as b by extracting values in numeric key order (lines 438-444), preserving list semantics
  • Applies the list_limit check with sensible defaults (20 when options is None)
  • Converts to OverflowDict when the limit is exceeded

The condition len(res) > list_limit correctly implements the overflow semantics described in the docstring, including edge cases like negative limits causing immediate overflow.

Docstring accuracy note: The docstring (line 396-399) states that combine "never raises an exception when the limit is exceeded" even if raise_on_limit_exceeded is True. This is accurate for the current implementation—overflow is handled silently by converting to OverflowDict. However, callers in decode.py may need to check the return type and raise if configured to do so. Ensure the calling code properly handles this case.

Based on coding guidelines, as per learnings: merge and combine should not inline limit enforcement logic elsewhere, which this implementation respects.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/qs_codec/utils/utils.py:
- Around line 210-225: The merge implementation in Utils.merge normalizes keys
with str(key) (variable normalized_key) which can collapse distinct non-string
keys (e.g., 1 vs "1"); update Utils.merge to either (a) enforce/validate that
callers only pass string keys (add a precondition check raising or converting
whole input and document it) or (b) avoid lossy normalization by keeping
original key types when merge_target contains that exact key (compare both key
and str(key) and prefer exact-type match), and update any usage of merge_target
and OverflowDict handling accordingly so behavior is deterministic and
documented.
- Around line 31-35: The review notes that OverflowDict is exported in __all__
despite the project's documented export allowlist; either remove OverflowDict
from the module export list in src/qs_codec/__init__.py (delete it from the
__all__ sequence) to keep OverflowDict internal, or explicitly add OverflowDict
to the documented export policy/allowlist so exports and docs match; locate the
OverflowDict symbol in src/qs_codec/utils/utils.py and the __all__ definition in
src/qs_codec/__init__.py and make the change consistently (remove OverflowDict
from __all__ or update the allowlist to include OverflowDict).
🧹 Nitpick comments (5)
tests/unit/utils_test.py (2)

12-13: Prefer importing OverflowDict from the public API (if it’s intended to be public).

If OverflowDict is now part of the supported surface, consider importing it from qs_codec (package root) instead of qs_codec.utils.utils to ensure tests track the public contract.


631-683: Nice coverage for list_limit overflow in Utils.combine.

These cases (default limit, zero, negative, options=None) are well-targeted and validate the core DoS mitigation behavior. Consider adding 1–2 small boundaries:

  • len(res) == list_limit should stay a list (no overflow).
  • list_limit < 0 with an empty result (do you want [] or OverflowDict({})?).
src/qs_codec/utils/utils.py (3)

31-35: OverflowDict as a marker type is fine; consider preserving the marker on .copy().

Since this is a semantic marker, overriding copy() / __copy__ to return OverflowDict (not dict) can prevent accidental “loss of overflow-ness” if callers copy it.


362-382: Helpers are clear; _numeric_key_pairs probably shouldn’t be “public” if it’s internal-only.

Even though it’s a @staticmethod, the leading underscore suggests internal use; if it’s not meant for consumers, consider moving it to module scope or dropping it from any public re-exports.


383-461: Fix combine() default/negative list_limit behavior to match DecodeOptions + docstring.

  • Default list_limit is hard-coded to 20; safer to derive from DecodeOptions() so defaults can’t drift.
  • Docstring says negative limit overflows “non-empty” results, but current code also overflows empty (len([]) > -1).
Proposed diff
@@
-        list_limit = options.list_limit if options else 20
-        if len(res) > list_limit:
+        list_limit = options.list_limit if options is not None else DecodeOptions().list_limit
+        if list_limit < 0:
+            return OverflowDict({str(i): x for i, x in enumerate(res)}) if res else []
+        if len(res) > list_limit:
             # Convert to OverflowDict
             return OverflowDict({str(i): x for i, x in enumerate(res)})
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ceef2ee and 50c930b.

📒 Files selected for processing (2)
  • src/qs_codec/utils/utils.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (5)
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/utils_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/utils_test.py
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • tests/unit/utils_test.py
  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to {src/qs_codec/enums/**/*.py,tests/unit/**/*.py} : For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • tests/unit/utils_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/utils/utils.py
🧬 Code graph analysis (1)
src/qs_codec/utils/utils.py (2)
src/qs_codec/models/undefined.py (1)
  • Undefined (17-69)
src/qs_codec/models/decode_options.py (1)
  • DecodeOptions (20-293)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (3)
tests/unit/utils_test.py (1)

892-1019: OverflowDict merge/combine semantics are well exercised (sparse keys, non-numeric keys, Undefined skipping, copy-on-write).

The tests around sparse numeric keys and non-numeric key handling make the intended semantics very clear and should help prevent regressions.

src/qs_codec/utils/utils.py (2)

151-155: Good: merge() delegates OverflowDict targets to combine() for append semantics.

This keeps overflow/list behavior centralized and avoids re-implementing list growth rules in multiple places. Based on learnings, this is the right direction.


178-197: OverflowDict source merge: offset + numeric ordering logic looks consistent with intended semantics.

One thing to double-check is the “duplicate numeric key after int-coercion” overwrite behavior (e.g., "01" vs "1"): your helper doc notes it, but ensure this matches the desired qs parity.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/qs_codec/models/overflow_dict.py (1)

9-15: Consider adding __deepcopy__() to preserve the overflow marker during deep copies.

The Utils.merge method (line 223 in utils.py) uses copy.deepcopy(target), which will not preserve the OverflowDict type when deep copying since __deepcopy__() is not implemented. This could cause overflow markers to be lost during recursive merges.

♻️ Suggested implementation
+    def __deepcopy__(self, memo: t.Dict[int, t.Any]) -> "OverflowDict":
+        """Return an OverflowDict deep copy to preserve the overflow marker."""
+        import copy
+        return OverflowDict({k: copy.deepcopy(v, memo) for k, v in self.items()})

Note: You'll need to add import typing as t at the top of the file for the type hint.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50c930b and e7d2461.

📒 Files selected for processing (5)
  • src/qs_codec/decode.py
  • src/qs_codec/models/overflow_dict.py
  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
  • tests/unit/utils_test.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/qs_codec/decode.py
  • tests/unit/utils_test.py
🧰 Additional context used
📓 Path-based instructions (5)
{src,tests}/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{src,tests}/**/*.py: Keep docstrings descriptive and parity-focused; match the existing module docstring style
Maintain 120-character line length (Black) and isort import ordering (black profile)

Files:

  • src/qs_codec/models/overflow_dict.py
  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
src/qs_codec/{decode.py,utils/utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Files:

  • src/qs_codec/utils/utils.py
src/qs_codec/utils/{utils.py,decode_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Files:

  • src/qs_codec/utils/utils.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Files:

  • tests/unit/decode_test.py
{src/qs_codec/enums/**/*.py,tests/unit/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For new list or formatting strategies, add an Enum member with associated generator/formatter and tests to cover serialization/deserialization round-trip

Files:

  • tests/unit/decode_test.py
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/utils/{utils.py,decode_utils.py} : Do not inline merge logic elsewhere; when altering merge or list/index logic, adjust Utils.merge or decoding helpers instead

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{decode.py,utils/utils.py} : Honor Duplicates policy during merge: COMBINE accumulates lists; FIRST/LAST semantics are enforced

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Preserve public function signatures; only add kwargs when necessary and maintain backward compatibility

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/__init__.py : Expose the public API only via __all__ in qs_codec/__init__.py, re-exporting: encode, decode, dumps, loads, Charset, Format, ListFormat, Duplicates, Sentinel, DecodeKind, EncodeOptions, DecodeOptions, Undefined

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to tests/unit/**/*.py : Add regression tests for limits (depth, list_limit, parameter_limit), duplicate policies, charset sentinel interactions, dot-notation toggles, and comma round-trip

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Do not mutate caller inputs; copy/normalize inputs before traversal (deepcopy for mappings; index-projection for sequences)

Applied to files:

  • src/qs_codec/utils/utils.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Enforce depth, list_limit, parameter_limit, strict_depth, and raise_on_limit_exceeded exactly as specified

Applied to files:

  • src/qs_codec/utils/utils.py
  • tests/unit/decode_test.py
📚 Learning: 2025-10-09T07:54:31.752Z
Learnt from: CR
Repo: techouse/qs_codec PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T07:54:31.752Z
Learning: Applies to src/qs_codec/{encode.py,decode.py} : Validate inputs/options and raise ValueError (or IndexError for strict depth) with messages matching tests where asserted

Applied to files:

  • tests/unit/decode_test.py
🧬 Code graph analysis (1)
tests/unit/decode_test.py (3)
src/qs_codec/models/overflow_dict.py (1)
  • OverflowDict (6-15)
src/qs_codec/decode.py (1)
  • decode (34-104)
src/qs_codec/utils/decode_utils.py (1)
  • decode (163-203)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (pypy3.10, pypy-3.10)
  • GitHub Check: Test (pypy3.8, pypy-3.8)
  • GitHub Check: Test (pypy3.11, pypy-3.11)
  • GitHub Check: Test (pypy3.9, pypy-3.9)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (10)
tests/unit/decode_test.py (2)

1694-1735: LGTM! Comprehensive CVE-2024 DoS regression tests.

The test coverage validates:

  • DoS attack scenario with 105 items exceeding limit of 100 produces OverflowDict
  • Edge cases: list_limit values of 0, 1, and 2
  • Boundary condition: exactly at limit (2 items, limit=2) returns list, not OverflowDict

The assertions correctly verify both the type (isinstance(result["a"], OverflowDict)) and the structure (numeric string keys).

Based on learnings: These tests align with the requirement to add regression tests for limits.


322-326: Test expectations correctly updated for zero list_limit overflow behavior.

The updated assertions expect {"a": {"0": "...", "1": "..."}} format when list_limit=0, which aligns with the overflow protection mechanism. The dict representation with numeric string keys prevents unbounded memory growth while preserving the data.

Based on learnings: These test updates correctly validate the list_limit enforcement as specified in coding guidelines.

Also applies to: 332-336, 584-584, 596-596, 1338-1340

src/qs_codec/utils/utils.py (8)

32-46: LGTM! Helper function correctly handles numeric key extraction.

The try/except block properly handles non-numeric keys, and the docstring appropriately warns about potential key collisions when distinct strings like "01" and "1" both coerce to the same integer.


377-380: LGTM! Clean abstraction for overflow detection.


189-208: LGTM! Overflow source merging correctly applies index offset.

The offset calculation at lines 195-197 ensures that when a non-Undefined target is prepended to an overflow source:

  • Target occupies index "0"
  • Source indices are shifted: source[k]result[str(k + 1)]

Non-numeric keys from the source are preserved as-is (lines 203-207), which maintains mixed key semantics.

Based on learnings: Honors merge semantics as specified in coding guidelines.


227-240: LGTM! Key merging logic correctly prioritizes exact matches.

The three-tier matching strategy (lines 228-235) prevents unintended key collisions:

  1. Exact key match (preserves type: 0 vs "0")
  2. String-normalized match (handles cross-type lookups)
  3. New key (adds to merged result)

Line 240 correctly preserves the OverflowDict type when the target was an overflow dict.


404-429: LGTM! OverflowDict-first combination correctly maintains numeric ordering.

When a is an OverflowDict:

  • Line 408: Creates a clean copy without Undefined values
  • Line 411: Safely computes next index using max + 1 or 0 if empty
  • Lines 413-428: Appends items from b (handling list, tuple, OverflowDict, and scalar cases)

The numeric key ordering is preserved via sorted(_numeric_key_pairs(...)) at line 421, ensuring list semantics are maintained.


431-460: Review the normal combination path with overflow protection.

The overflow conversion logic is sound:

  • Line 454: Negative list_limit → immediate OverflowDict conversion
  • Line 456: len(res) > list_limit → conversion (note: uses > not >=, so at-limit stays a list)
  • Lines 441-447: OverflowDict source is flattened to a list in numeric-key order

The Undefined filtering throughout (lines 434, 440, 446, 449) ensures sentinels are never included in the combined result.

Based on learnings: Correctly enforces list_limit as specified in coding guidelines.


454-455: No action needed—the behavior is documented and intentional.

The code correctly returns an empty list when list_limit < 0 and the result is empty. This behavior is explicitly documented in the docstring (lines 397–398: "any non-empty combined result will be converted to OverflowDict") and verified by the test test_combine_negative_list_limit_with_empty_result_stays_list, which asserts that Utils.combine([], [], DecodeOptions(list_limit=-1)) returns an empty list. The conditional pattern if res else res matches the documented design.


163-164: Delegation to combine() when target is OverflowDict is correct.

The combine() function has explicit logic to handle OverflowDict as the first argument, creating a copy and appending subsequent values at sequential numeric indices while filtering Undefined values. This properly preserves overflow semantics during merge operations and follows the architecture principle of centralizing merge logic in utility helpers.

@techouse techouse merged commit 8c2de54 into main Jan 11, 2026
23 checks passed
@techouse techouse deleted the fix/implement-list-limit-handling-in-combine-function-to-prevent-DoS-via-memory-exhaustion branch January 11, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants