Skip to content

fix(validation): handle multipleOf float precision for charging schedule limit fields (Issue #18)#35

Closed
duyhuynh-vn wants to merge 1 commit into
mainfrom
nightly/2026-06-14-issue-18
Closed

fix(validation): handle multipleOf float precision for charging schedule limit fields (Issue #18)#35
duyhuynh-vn wants to merge 1 commit into
mainfrom
nightly/2026-06-14-issue-18

Conversation

@duyhuynh-vn

Copy link
Copy Markdown
Collaborator

Summary

Closes #18.

Three OCPP 1.6J schemas (SetChargingProfile, RemoteStartTransaction, GetCompositeSchedule response) declare "multipleOf": 0.1 on charging-rate limit / minChargingRate fields. serde_json represents JSON numbers as f64, so 21.4_f64 / 0.1_f64 = 213.9999… rather than 214.0. jsonschema 0.17's MultipleOfFloatValidator computes (item / multiple_of) % 1.0 and only checks remainder < f64::EPSILON, so a semantically-valid 1-decimal value like 21.4 is incorrectly rejected.

This mirrors the precision problem the Python reference documents and works around in ocpp/messages.py _validate_payload() (it re-parses the payload with decimal.Decimal).

What was ported

Faithful port of the Python decimal.Decimal re-parse, written idiomatically in Rust as a pre-normalisation pass — no new dependencies, single file changed (crates/ocpp-messages/src/schema_validation.rs, +187/−17):

  • collect_float_divisors(schema) — recursively walks the JSON Schema and collects every "multipleOf" divisor with a non-zero fractional part (only 0.1 in OCPP 1.6J).
  • normalize_floats(payload, divisors) — for each positive f64 x, replaces it with round(x / d) * d when |candidate − x| < 1e-9 · max(1, |x|). Empirically (214 * 0.1_f64) / 0.1_f64 == 214.0 exactly in IEEE 754, so the normalised value passes jsonschema's % 1.0 < EPSILON check. Values that are not near an exact multiple (e.g. 21.41 → would round to 4.1, delta 0.01 ≫ 1e-9) are left unchanged and still fail validation.
  • run_validation() — 4-line addition to normalise the payload before compiling/validating, only when float divisors are present (zero overhead for the other 75 schemas).

Test plan

cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test --workspace all pass locally (verified on a local merge with current main; branch merges cleanly).

New tests (5), porting the three Python reference cases plus edge cases:

Test Asserts
validate_set_charging_profile_float_limit_passes limit: 21.4 passes (was 21.0 workaround)
validate_get_composite_schedule_response_float_limit_passes limit: 15.2 passes (was 15.0)
validate_remote_start_transaction_float_limit_passes limit: 11.5 passes
validate_set_charging_profile_float_min_charging_rate_passes minChargingRate: 6.6 passes
validate_set_charging_profile_two_decimal_limit_fails limit: 21.41 still correctly rejected

Known gaps

Python reference

  • ocpp/messages.py_validate_payload(), "3 OCPP 1.6 schedules have fields of type floats" comment
  • tests/test_messages.pytest_validate_set_charging_profile_payload, test_validate_get_composite_profile_payload

🌙 Automated nightly dev. Please review — I will not merge my own PR.


Generated by Claude Code

…ule limit fields (Issue #18)

jsonschema 0.17 MultipleOfFloatValidator computes (x / 0.1_f64) % 1.0 and
checks remainder < f64::EPSILON. For 21.4_f64 / 0.1_f64 = 213.999…, the
remainder is 0.999… which fails the check even though 21.4 is a valid
1-decimal value. Mirrors the Python reference fix in ocpp/messages.py which
re-parses payloads using decimal.Decimal for exact arithmetic.

Fix: before handing the payload to jsonschema, walk the JSON Schema to
collect all float-valued multipleOf divisors (only 0.1 in OCPP 1.6J), then
replace each matching float x in the payload with round(x/d)*d. Empirically
214 * 0.1_f64 / 0.1_f64 = 214.0 exactly in IEEE 754, so the normalised value
passes the remainder check. Values that are NOT near a 1-decimal multiple
(e.g. 21.41, delta 0.01 >> 1e-9 tolerance) are left unchanged and continue
to fail validation correctly. No new dependencies.

New tests (5): float limit 21.4 in SetChargingProfile, float limit 15.2 in
GetCompositeScheduleResponse, float limit 11.5 in RemoteStartTransaction,
float minChargingRate 6.6 in SetChargingProfile, and 21.41 still rejected.

https://claude.ai/code/session_01AthhodqFKNgao8nPcA12aP
@duyhuynh-vn

Copy link
Copy Markdown
Collaborator Author

🧹 Grooming note (nightly dev) — this PR looks superseded.

Issue #18 was resolved by the alternative implementation in #34 (exact-decimal check), which merged at 01:11Z — and #18 auto-closed with it. This PR (#35) is the other approach for the same issue (payload normalization on branch nightly/2026-06-14-issue-18), so it's now redundant and would re-touch schema_validation.rs that #34 already fixed on main.

Recommend closing #35 (and optionally deleting its branch). Leaving the call to you since the two approaches were explicitly offered as a choice and you picked #34. Flagging rather than closing it myself.

Copy link
Copy Markdown
Collaborator Author

🌙 Closing as superseded.

Issue #18 was resolved and closed by PR #34 (merged to main as 4fd77f1), which took the exact-decimal-multiple approach. This PR (#35) is the alternate payload-mutation implementation of the same fix — the #34 description offered both and the maintainer merged #34. With #18 already closed, #35 can no longer close it and only duplicates landed work.

Closing to keep the nightly PR queue clean and free a slot under the 2-open-PR cap. No code is lost — the merged fix on main already covers the multipleOf: 0.1 precision case (verified: validate_set_charging_profile_with_decimal_limit_passes etc. are green on main).


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

🌙 Closing as superseded. Issue #18 was resolved and closed by the merged PR #34, which fixes the same multipleOf: 0.1 f64 precision problem with a multipleOf-error filter (is_multiple_of_precision_artifact) in run_validation. This PR's normalize_floats pre-pass targets the same now-closed issue and is based on the pre-#34 main, so it's redundant.

No functionality is lost — the precision fix is already on main. Closing to keep the nightly queue under the 2-PR cap. If a reviewer prefers the normalization approach over the filter approach, happy to revisit on a fresh issue.


Generated by Claude Code

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.

fix(validation): handle multipleOf float precision for SetChargingProfile/RemoteStartTransaction/GetCompositeSchedule

2 participants