Skip to content

fix: validate bbox input and guard empty geocoder results - #530

Open
eklavya072 wants to merge 3 commits into
UNDP-Data:mainfrom
eklavya072:test/bbox-param-validation
Open

fix: validate bbox input and guard empty geocoder results#530
eklavya072 wants to merge 3 commits into
UNDP-Data:mainfrom
eklavya072:test/bbox-param-validation

Conversation

@eklavya072

@eklavya072 eklavya072 commented Aug 21, 2026

Copy link
Copy Markdown

overview

BboxParamType only checked that it got four numbers, so a fair bit of unusable
input was getting through — nan, coordinates off the globe, inverted boxes.
this adds tests for those cases and the validation to reject them.

what changed

convert() now rejects non-finite ordinates, lon/lat outside -180/180 and
-90/90, and min >= max, which handles the inverted and zero-area cases in one
rule. the finite check runs first because nan compares False against everything
and was slipping straight through the range check. bounds match the ones
cmask.py already uses.

buffer_bbox clamps latitude to ±90 so a bbox already touching a pole doesn't
get pushed past it. longitude is left alone since a bbox crossing the antimeridian
looks like a legitimate AOI, and mgrsconv wraps longitude elsewhere anyway.

get_bbox_label / get_best_semantic_label were indexing rg.search(...)[0]
with no guard, so an AOI with no geocoder match came out as an IndexError. both
raise BadParameter now. get_bbox_label's -> str annotation is also now
-> dict, which is what it actually returns.

testing

tests/rapida/test_bbox_param_type.py covers all four functions. the edge cases
went in as xfail(strict=True) so they'd flip the moment the behaviour changed —
they're plain tests now. 38 passing, and the example in admin.py still parses.

one thing i'm unsure about: rejecting a zero-area bbox is the only change here
that isn't purely a bug fix. happy to drop it if a degenerate bbox is meaningful
somewhere.

files:

  • rapida/util/bbox_param_type.py
  • tests/rapida/test_bbox_param_type.py

@eklavya072

eklavya072 commented Aug 25, 2026

Copy link
Copy Markdown
Author

Flagging this in case it slipped past — happy to adjust anything here.

This is test-only (tests/rapida/test_bbox_param_type.py, +300/−0); no runtime code is touched. Beyond covering the paths for BboxParamType.convert, buffer_bbox, get_bbox_label and get_best_semantic_label, the xfail(strict=True) cases document six validation gaps I hit while reading the code:

  1. Non-finite ordinates are acceptednan,nan,nan,nan and 1,2,inf,4 pass float() and are returned unchanged.
  2. No lon/lat range check200,10,210,20 and 10,100,20,110 are accepted.
  3. No min < max check — an inverted bbox like 10,0,5,1 is accepted.
  4. Zero-area bbox accepted5,5,5,5 is passed downstream rather than failing early.
  5. buffer_bbox can push latitude past 90°max(cos, 1e-6) guards the longitude divisor, but nothing clamps maxlat + dlat.
  6. An empty geocoder result raises IndexErrorrg.search(...)[0] is indexed unconditionally in both get_bbox_label and get_best_semantic_label, so the failure surfaces as IndexError rather than click.BadParameter.

One small extra: get_bbox_label is annotated -> str but returns the raw geocoder dict. The tests assert the current dict behaviour rather than the annotation — happy to flip that if the annotation is the intended contract.

I used xfail(strict=True) so each case flips to a failure the moment it's fixed, rather than silently passing. If you'd rather these were skip, or split from the passing tests into a separate PR, I'm glad to restructure.

I think this needs maintainer approval for workflow runs on a first-time fork contribution. I'll rebase onto latest main whenever that's useful.

If a regression test around #491 would help, I'm happy to look at that next.

@iferencik

Copy link
Copy Markdown
Contributor

@eklavya072 thank you for submitting this PR. You are spot on with those 6 use cases, all of them are valid. The BboxParamType is very primitive (expects only a sequence of 4 values) . It would be nice to enhance the validation with your edge cases

eklavya072 and others added 2 commits September 2, 2026 05:02
convert() rejects non-finite ordinates, lon/lat outside -180/180 and -90/90,
and min >= max, which covers the inverted and zero-area cases. nan is checked
before the range because it compares False against everything and was going
straight through.

buffer_bbox clamps latitude so a polar bbox is not pushed past 90. longitude is
left alone, a bbox crossing the antimeridian is a valid AOI.

get_bbox_label and get_best_semantic_label guard rg.search(...) instead of
indexing the first hit blind, so an AOI with no match raises BadParameter rather
than IndexError. get_bbox_label's return annotation now matches the dict it
returns.

the xfail(strict=True) cases from the previous commit are plain tests now.
@eklavya072 eklavya072 changed the title test: implementing boundary exception checks for bbox param type fix: validate bbox input and guard empty geocoder results Sep 2, 2026
@eklavya072

eklavya072 commented Sep 2, 2026

Copy link
Copy Markdown
Author

thanks for looking at it. pushed the validation.

convert() now rejects non-finite ordinates, lon/lat out of range, and min >= max,
which handles the inverted and zero-area cases in one rule. nan had to be checked
before the range check since it compares False against everything and was slipping
straight through.

also clamped latitude in buffer_bbox so a bbox already at a pole doesn't get pushed
past 90, and guarded the rg.search(...)[0] indexing in both label functions. left
longitude unclamped since a bbox crossing the antimeridian looks like a legitimate
AOI, and mgrsconv wraps longitude elsewhere anyway. used the same -180/180, -90/90
bounds cmask.py already uses.

heads up this isn't test-only anymore like my earlier comment said, it touches
rapida/util/bbox_param_type.py now. the 12 xfail cases are plain tests since they
pass. 38 passing locally, and the documented example in admin.py still parses.

two things i wasn't so sure on:

rejecting a zero-area bbox (5,5,5,5) is the only change that's a behaviour call
rather than a straight bug fix. easy to drop if a degenerate bbox means something
somewhere.

the label functions raise BadParameter, which fits the CLI path but is a bit loose
when bbox comes from gdf.total_bounds in connectivity. happy to switch it if you'd
rather something else there.

renamed the PR since "test:" doesn't really fit now.

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