Skip to content

Conversation

@techouse
Copy link
Owner

@techouse techouse commented Feb 1, 2026

This pull request introduces significant improvements to the qs_dart package's encoding and decoding logic, focusing on making the encoder iterative (stack-based) instead of recursive, improving cycle detection, and strengthening option validation. The changes also clarify documentation and error handling for decode options.

Key changes include:

Encoding Refactor and Improvements

  • Refactored the core encoder in encode.dart from a recursive implementation to an iterative, stack-based approach using an explicit stack of EncodeFrame objects. This removes recursion limits, improves cycle detection, and makes the code more maintainable and robust. [1] [2] [3] [4] [5] [6]
  • Added a new internal enum MergePhase in merge_phase.dart to drive state machines for iterative merges, supporting future improvements and clarifying merge phases.

Decode Options Validation and Documentation

  • Improved documentation and error messages for DecodeOptions, clarifying that invalid combinations (such as decodeDotInKeys: true with allowDots: false) will throw when validated or used, not just at construction. [1] [2] [3]
  • Added a validate() method to DecodeOptions that enforces invariants at runtime, ensuring options are correct before decoding. This method is called in decode entry points and direct decoder calls. [1] [2]
  • Added a throwOnLimitExceeded option to DecodeOptions.copyWith, allowing more granular control over decode behavior.

These changes collectively improve the reliability, maintainability, and clarity of the encoding/decoding logic in the package.

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces recursive encoding and merging with iterative, frame-driven engines (EncodeFrame / MergeFrame); defers constructor asserts into runtime validate() for EncodeOptions/DecodeOptions (DecodeOptions adds throwOnLimitExceeded and an Expando cache); QS entrypoints call options.validate(); tests and helpers added for edge cases and deep nesting.

Changes

Cohort / File(s) Summary
Iterative encoder
lib/src/extensions/encode.dart, lib/src/models/encode_frame.dart
Reworks encoder from recursive/sentinel-based traversal to an iterative stack of EncodeFrame instances: per-frame lifecycle, per-frame cycle detection, onResult aggregation, list-format/null/undefined handling; removes global traversal state.
Iterative merge engine
lib/src/utils.dart, lib/src/models/merge_frame.dart, lib/src/enums/merge_phase.dart
Replaces recursive merge with a phased, non-recursive state machine using MergeFrame and MergePhase; adds start/mapIter/listIter phases, overflow/indexed handling, hole handling and multi-phase result propagation.
Options validation & QS hooks
lib/src/models/encode_options.dart, lib/src/models/decode_options.dart, lib/src/qs.dart
Moves constructor asserts to new validate() methods; DecodeOptions adds throwOnLimitExceeded and an Expando cache; QS.encode/QS.decode call options.validate().
Utils & charset / ByteBuffer handling
lib/src/utils.dart, .github/copilot-instructions.md
Shifts charset/filter checks to runtime validation; decodes ByteBuffer with allowMalformed for UTF‑8; adjusts merge/encode flows for mixed shapes and Undefined semantics.
Tests & test helpers
test/unit/*, test/support/fake_encoding.dart
Adds and updates unit tests for runtime validation, broadened charset error expectations, ByteBuffer encoding, deep‑nesting (2048) encode/merge tests, encode edge cases (custom objects, undefined), and a FakeEncoding helper.
Docs / Changelog
CHANGELOG.md, .github/copilot-instructions.md
Adds 1.7.0-wip changelog entries and updates internal notes on ByteBuffer/merge semantics and encode behaviour.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant QS as QS.encode
    participant Validate as EncodeOptions.validate()
    participant Encoder as Iterative Encoder
    participant Frame as EncodeFrame Stack
    participant Result as Result Accumulator

    Client->>QS: encode(object, options)
    QS->>Validate: options.validate()
    alt Validation fails
        Validate-->>QS: throw
        QS-->>Client: Error
    else Validation passes
        QS->>Encoder: start iterative encoding
        Encoder->>Frame: push root EncodeFrame
        loop process frames
            Frame->>Frame: prepare (keys, cycle detection)
            alt primitive / fast-path
                Frame->>Result: emit fragment
            else nested value
                Frame->>Frame: push child EncodeFrame
            end
            Frame->>Result: onResult aggregates fragments
        end
        Result-->>QS: aggregated query string
        QS-->>Client: return string
    end
Loading
sequenceDiagram
    participant Client as Client Code
    participant QS as QS.decode
    participant Options as DecodeOptions
    participant Validator as Options.validate()
    participant Parser as Tokeniser/Parser
    participant Decoder as Iterative Merge Engine
    participant MergeFrame as MergeFrame Stack
    participant Result as Decoded Object

    Client->>QS: decode(qs, options)
    QS->>Options: options.validate()
    alt Validation fails
        Options-->>QS: throw
        QS-->>Client: Error
    else Validation OK / cached
        QS->>Parser: parse tokens
        Parser->>Decoder: walk tokens
        Decoder->>MergeFrame: push root MergeFrame
        loop process frames
            MergeFrame->>MergeFrame: normalize shape, iterate keys/indices
            alt nested map/list
                MergeFrame->>MergeFrame: spawn child MergeFrame
            else leaf value
                MergeFrame->>Result: set value
            end
            MergeFrame->>Decoder: onResult propagate
        end
        Decoder-->>QS: decoded object
        QS-->>Client: return object
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through frames both wide and deep,
No recursive cliffs to make me weep.
Keys and lists now tumble in line,
Deep nests stand firm — no stack to unwind.
A carrot of code, compact and sweet.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title ':zap: general improvements' is vague and generic, using a non-descriptive term that does not convey meaningful information about the changeset. Replace with a more specific title highlighting the primary change, such as 'Refactor encoder to iterative stack-based approach' or 'Add runtime validation for decode options'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive and covers key changes including encoding refactor, decode options validation, and improvements. However, it does not follow the provided template structure with required sections like 'Type of change' checkboxes and 'How Has This Been Tested?' details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/general-improvements

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.

@techouse techouse requested a review from Copilot February 1, 2026 19:39
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 PR refactors the core encoding and decoding logic to improve robustness and maintainability by replacing recursion with iterative stack-based approaches, while also moving option validation from constructor-time to usage-time.

Changes:

  • Replaced recursive encoding with an iterative stack-based approach using _EncodeFrame for better cycle detection and to prevent stack overflow on deeply nested structures.
  • Replaced recursive merging in Utils.merge with an iterative stack-based approach using _MergeFrame to handle deep maps without stack overflow.
  • Moved DecodeOptions and EncodeOptions validation from constructor assertions to dedicated validation methods called at usage time (QS.decode/QS.encode), providing clearer error handling.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/src/extensions/encode.dart Replaced recursive _encode with iterative stack-based traversal using _EncodeFrame; removed _sentinel WeakMap anchor
lib/src/models/encode_frame.dart Introduced new _EncodeFrame class to track encoder state across iterative traversal
lib/src/utils.dart Replaced recursive Utils.merge with iterative stack-based approach using _MergeFrame and _MergePhase; improved ByteBuffer charset handling
lib/src/models/decode_options.dart Moved validation from constructor assertions to validate() method; added Expando cache for validated instances; added throwOnLimitExceeded to copyWith and toString
lib/src/models/encode_options.dart Removed constructor assertions for charset and filter validation
lib/src/qs.dart Added validation calls before encoding/decoding; introduced _validateEncodeOptions helper; added encode_frame.dart part
test/unit/utils_test.dart Added test for deep map merging without stack overflow
test/unit/encode_edge_cases_test.dart Added test for deep nesting encoding without stack overflow
test/unit/models/decode_options_test.dart Updated tests to validate options at usage time rather than construction; added runtime validation tests; added _FakeEncoding helper
test/unit/models/encode_options_test.dart Added runtime validation tests with _FakeEncoding helper
test/unit/decode_test.dart Made DecodeOptions constructors const where possible; updated error expectation matchers
test/unit/uri_extension_test.dart Updated error expectation matchers to include ArgumentError and StateError

@codacy-production
Copy link

codacy-production bot commented Feb 1, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-1.20% (target: -1.00%) 92.46%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (f8f06cc) 955 935 97.91%
Head commit (2e6d9be) 1124 (+169) 1087 (+152) 96.71% (-1.20%)

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 (#47) 345 319 92.46%

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 Feb 1, 2026

Codecov Report

❌ Patch coverage is 94.39776% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.27%. Comparing base (f8f06cc) to head (da20f31).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
lib/src/utils.dart 94.94% 9 Missing ⚠️
lib/src/models/decode_options.dart 66.66% 7 Missing ⚠️
lib/src/extensions/encode.dart 97.27% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #47      +/-   ##
==========================================
- Coverage   97.90%   97.27%   -0.64%     
==========================================
  Files          14       16       +2     
  Lines         954     1136     +182     
==========================================
+ Hits          934     1105     +171     
- Misses         20       31      +11     

☔ 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.

@techouse techouse marked this pull request as ready for review February 1, 2026 19:58
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@techouse
Copy link
Owner Author

techouse commented Feb 1, 2026

@codex review

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@techouse techouse added the enhancement New feature or request label Feb 1, 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: 2

Caution

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

⚠️ Outside diff range comments (1)
lib/src/models/decode_options.dart (1)

53-78: ⚠️ Potential issue | 🟠 Major

Add debug-time asserts to the const constructor for option invariants.

The current implementation defers all validation to the validate() method, allowing invalid option combinations to be constructed without immediate feedback. Project guidance requires option constructors to validate invariants via assert statements. Dart const constructors support asserts in the initialiser list—when invoked with const, a failing assert produces a compile-time error for that instantiation.

Add asserts for:

  • charset must be UTF-8 or Latin-1
  • decodeDotInKeys: true requires allowDots to be truthy
  • parameterLimit must be positive

Tests that intentionally construct invalid options will need to use non-const instances (removing const keyword) to allow the asserts to fail at runtime during decode.

♻️ Proposed asserts for initialiser list
  })  : allowDots = allowDots ?? (decodeDotInKeys ?? false),
        decodeDotInKeys = decodeDotInKeys ?? false,
        _decoder = decoder,
-       _legacyDecoder = legacyDecoder;
+       _legacyDecoder = legacyDecoder,
+       assert(
+         charset == utf8 || charset == latin1,
+         'Invalid charset',
+       ),
+       assert(
+         !(decodeDotInKeys ?? false) ||
+             (allowDots ?? (decodeDotInKeys ?? false)),
+         'decodeDotInKeys requires allowDots to be true',
+       ),
+       assert(
+         parameterLimit > 0,
+         'Parameter limit must be a positive number.',
+       );
🤖 Fix all issues with AI agents
In `@lib/src/utils.dart`:
- Around line 165-173: The normalization branch that runs when
frame.options?.parseLists == false returns a SplayTreeMap with int keys and is
skipped by the post-list-merge path, so change both places (the branch around
frame.options?.parseLists and the similar code at the other location) to: after
list merges run normalization that converts keys to string (use
entry.key.toString()) and filter out Undefined values, and ensure frame.onResult
is called with a Map<String, dynamic> (not SplayTreeMap<int, dynamic>) so
array-shaped results never contain Undefined; update the code around target_,
stack.removeLast(), and frame.onResult to produce the normalized Map<String,
dynamic> in both branches.
- Around line 149-201: The hole-handling branch that runs when
currentTarget.any((el) => el is Undefined) currently executes before the
list-of-maps merge logic, causing lists-of-maps with holes to be flattened
instead of merged by index; compute or check whether both sides are map-lists
(e.g., bool targetMaps = currentTarget.every((el) => el is Map || el is
Undefined); bool sourceMaps = currentSource is Iterable ?
currentSource.every((el) => el is Map || el is Undefined) : false) and guard the
hole branch so it only runs when NOT (targetMaps && sourceMaps); alternatively
move the targetMaps/sourceMaps computation above the Undefined check and skip
the early hole shortcut when targetMaps && sourceMaps so the code falls through
to the logic that sets frame.indexedTarget, frame.sourceList, frame.targetIsSet,
frame.listIndex and frame.phase = _MergePhase.listIter.
🧹 Nitpick comments (1)
lib/src/utils.dart (1)

15-18: Use PascalCase for _MergePhase values.

The enum values start, mapIter, and listIter don’t follow the PascalCase rule for enum members. Please rename them (and update references) to match the project convention. As per coding guidelines: Use PascalCase for enum member names.

@techouse techouse requested a review from Copilot February 1, 2026 21:02
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

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

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 `@lib/src/enums/merge_phase.dart`:
- Around line 7-15: Rename the enum members in _MergePhase from start, mapIter,
listIter to PascalCase Start, MapIter, ListIter and update all call sites
referencing those members (there are 7 references across the codebase) so they
use the new names; specifically modify the definition in _MergePhase and then
update usages in the functions and classes in utils (e.g., any code referencing
_MergePhase.start/_MergePhase.mapIter/_MergePhase.listIter) and in
MergeFrame-related code (e.g., any references in the MergeFrame class) to the
corresponding PascalCase names.

@techouse
Copy link
Owner Author

techouse commented Feb 9, 2026

@codex review

@codacy-production
Copy link

codacy-production bot commented Feb 9, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-0.63% (target: -1.00%) 94.41%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (f8f06cc) 955 935 97.91%
Head commit (da20f31) 1137 (+182) 1106 (+171) 97.27% (-0.63%)

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 (#47) 358 338 94.41%

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

@techouse techouse self-assigned this Feb 9, 2026
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

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9d8982536

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9d8982536

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@techouse techouse requested a review from Copilot February 9, 2026 09:39
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

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

@techouse techouse requested a review from Copilot February 9, 2026 10:18
@techouse
Copy link
Owner Author

techouse commented Feb 9, 2026

@codex review

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

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

techouse and others added 2 commits February 9, 2026 10:45
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@techouse techouse requested a review from Copilot February 9, 2026 13:23
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

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

@techouse techouse merged commit 1db0b8d into main Feb 9, 2026
12 of 14 checks passed
@techouse techouse deleted the chore/general-improvements branch February 9, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant