Skip to content

Refuse a dimension sent with no value instead of filing it at slot 0 - #9

Merged
renatex314 merged 2 commits into
masterfrom
fix/refuse-valueless-dimension
Oct 5, 2026
Merged

renatex314 merged 2 commits into
masterfrom
fix/refuse-valueless-dimension

Conversation

@renatex314

Copy link
Copy Markdown
Owner

Fixes #8.

Problem

dims: {"cliId": null} was accepted, and the device was filed under value 0 of the dimension. On a dimension declared with a capacity, slot 0 is the first value ever reported, so every filter on that value also matched every null-reporting device. In the instance from the issue, f.cliId=65 returned 1126 devices instead of 1. When the filter is used to restrict a map to one client, that client sees devices that aren't theirs.

Cause

Schema::cells_for treated a value list that was present but empty the same as a dimension that was absent, and put both at index 0. null and [] both deserialise to an empty list.

Change

  • schema.rs: a dimension sent with no value (null or []) is now refused with a 400. The message names the dimension, and the route adds the device id:
    device "orphan": "cliId" has no value (null or an empty list); report an explicit value, such as "none", for a device that has none.
    Compact reports, metadata patches and GeoJSON all go through this check.
  • geojson.rs:
    • A null under a dimension's name in GeoJSON properties used to be read as an absent property, which also ends up at 0. It is now refused the same way as the compact form.
    • A null inside a list ([1, null], [null]) used to be dropped silently. It is now rejected while the body is parsed: 422 unprocessable_body, the same response a fractional value gets.
  • README.md: one sentence describing the rule.

Behaviour change

Clients that send null or [] for a dimension now get an error instead of a silent mis-filing. The workaround from the issue still works: report an explicit value (e.g. 0 or "none"), which gets its own slot.

Not in this PR

A dimension that is absent still takes value 0, as documented. That covers a new device reported without dims, and a report that names some dimensions but not others. On a capacity dimension this has the same exposure. Fixing it means reserving a slot for "unassigned" (which affects snapshots), so it should be its own change.

Testing

  • New HTTP regression test a_dimension_with_no_value_is_refused_not_filed_under_the_first_one (tests/regressions.rs). It replays the issue's setup and sends null, [] and [null] in compact form, in GeoJSON and as a metadata patch. It checks that each is rejected with the dimension and the device (or GeoJSON feature) named, that nothing is stored, and that f.cliId=65 still totals 1. Against the old code it fails with {"accepted":1,...}, which is the bug.
  • Updated geojson.rs unit tests for the new null handling.
  • cargo test --workspace (run in rust:1.98-slim-bookworm): all 218 tests pass.

🤖 Generated with Claude Code

renatex314 and others added 2 commits October 5, 2026 11:08
A report carrying `dims: {"cliId": null}` was accepted, and the device
was filed under value 0 of that dimension. On a dimension declared with
a `capacity`, slot 0 is the first value ever reported, so every filter
on that value matched every null-reporting device: in the instance
behind #8, `f.cliId=65` returned 1126 devices for 1 real one. Used as a
per-client restriction, that shows one client's map to another.

The cause was `Schema::cells_for` reading a present-but-empty value
list -- which is what `null` and `[]` deserialise to -- the same way as
an absent dimension. It now refuses one, naming the dimension; the
route already names the device, so the 400 reads
`device "orphan": "cliId" has no value (null or an empty list); ...`.
The check sits in the schema, so compact reports, metadata patches and
GeoJSON all go through it.

GeoJSON needed two more changes to reach it. A `null` under a
dimension's name in `properties` was read through `Option` as an absent
property, which also lands at 0; it is now present and empty, and
refused like the compact form. A `null` inside a list (`[1, null]`,
`[null]`) silently vanished; it is now a parse error, the same 422 a
fractional value gets.

A dimension that is absent altogether still takes value 0, as
documented. That has the same exposure on a capacity dimension and is
left for its own change, since closing it means reserving a slot.

Fixes #8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@renatex314
renatex314 merged commit a75c7d9 into master Oct 5, 2026
3 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.

dims com null é aceito em silêncio e o ponto passa a casar com o filtro do primeiro valor da dimensão

1 participant