Skip to content

fix: don't raise ArgumentError when filters map has non-integer keys#655

Merged
woylie merged 2 commits into
woylie:mainfrom
linhyojee:fix/filters-to-list-non-integer-keys
May 15, 2026
Merged

fix: don't raise ArgumentError when filters map has non-integer keys#655
woylie merged 2 commits into
woylie:mainfrom
linhyojee:fix/filters-to-list-non-integer-keys

Conversation

@linhyojee
Copy link
Copy Markdown
Contributor

Summary

Flop.Meta.filters_to_list/1 calls String.to_integer/1 directly on every key of the filters map, raising ArgumentError on any non-integer key. This causes Flop to crash with an uncaught exception before it can return its documented {:error, %Flop.Meta{}} validation tuple.

The crash also affects Flop's own error-construction path: Flop.Meta.with_errors/3 calls filters_to_list/1 when building the params field of an error meta, so once a malformed map reaches Flop, even constructing the error tuple re-raises. That makes it impossible for callers to recover via the public Flop.validate/2 / Flop.validate_and_run/3 contract.

Repro

iex(1)> Flop.validate(%{"filters" => %{"name" => "foo"}})
** (ArgumentError) errors were found at the given arguments:

  * 1st argument: not a textual representation of an integer

    (erts 16.2) :erlang.binary_to_integer("name")
    (flop 0.26.3) lib/flop/meta.ex:181: anonymous fn/1 in Flop.Meta.filters_to_list/1
    (elixir 1.19.5) lib/enum.ex:1696: anonymous fn/3 in Enum.map/2
    (stdlib 7.2) maps.erl:894: :maps.fold_1/4
    (elixir 1.19.5) lib/enum.ex:2532: Enum.map/2
    (flop 0.26.3) lib/flop/meta.ex:181: Flop.Meta.filters_to_list/1
    (flop 0.26.3) lib/flop/meta.ex:167: Flop.Meta.with_errors/3
    (flop 0.26.3) lib/flop.ex:1382: Flop.validate/2
    iex:1: (file)
iex(1)> 

Observed in production from a misconfigured client sending ?filters[transport_type_code]=SEA. The map-keyed-by-field name form is invalid input, but it should surface as a validation error rather than an uncaught exception.

Change

Replace String.to_integer/1 with a parse_filter_index/1 helper that accepts integer keys as-is and uses Integer.parse/1 for string keys, requiring the entire string to parse. Entries with non-integer keys are dropped silently from the resulting list - matching how the fallthrough clause already treats filters values that aren't in the indexed-map shape. Behaviour for all currently-valid inputs is unchanged.

Tests

Added in test/base/flop/meta_test.exs:

  • Regression: filters are sorted by numeric index, not lexicographic
  • New: non-integer keys are dropped, mixed maps keep only valid entries
  • New: field-name-keyed map no longer raises
  • Regression: list-form filters pass through untouched
  • Round-trip: Flop.validate/2 returns {:error, %Flop.Meta{}} for a field-name-keyed filter map

Flop.Meta.filters_to_list/1 called String.to_integer/1 directly on
every key of the filters map, raising ArgumentError on any non-integer
key (e.g. ?filters[name]=value). This also broke
Flop.Meta.with_errors/3, preventing Flop from constructing the
{:error, %Flop.Meta{}} tuple it normally returns for invalid filter
shapes.

Entries with non-integer keys are now dropped silently, consistent
with how other malformed filter inputs are handled.
@linhyojee linhyojee requested a review from woylie as a code owner May 13, 2026 05:43
Comment thread lib/flop/meta.ex Outdated
|> Enum.flat_map(fn {index, filter} ->
case parse_filter_index(index) do
{:ok, n} -> [{n, filter}]
:error -> []
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Wouldn't you want to see the original parameters in the error struct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point 👍

filters_to_list/1 is only called from with_errors/3, so its output ends up in meta.params. Silently dropping malformed entries hides exactly the information the caller needs to debug (e.g, "here's the filters[name]=foo you sent, here's why it's wrong"...etc).

Updated (commit: 1ad0d9f): when any key can't be parsed as integer, leave the input filters untouched rather than partially transforming it. The happy path (all integer indexed keys) unchanged, and a malformed map goes into meta.params so the caller sees exactly what they sent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @woylie Are you happy with the update/change?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Looks good, thank you!

@linhyojee linhyojee requested a review from woylie May 13, 2026 08:49
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.41%. Comparing base (e7f04ae) to head (1ad0d9f).
⚠️ Report is 14 commits behind head on main.

Files with missing lines Patch % Lines
lib/flop/meta.ex 85.71% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #655      +/-   ##
==========================================
- Coverage   84.45%   84.41%   -0.05%     
==========================================
  Files          15       15              
  Lines        1023     1033      +10     
==========================================
+ Hits          864      872       +8     
- Misses        159      161       +2     

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

@woylie woylie merged commit cea156e into woylie:main May 15, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants