fix(validation): handle multipleOf float precision for charging schedule limit fields (Issue #18)#35
fix(validation): handle multipleOf float precision for charging schedule limit fields (Issue #18)#35duyhuynh-vn wants to merge 1 commit into
Conversation
…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
|
🧹 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 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. |
|
🌙 Closing as superseded. Issue #18 was resolved and closed by PR #34 (merged to 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 Generated by Claude Code |
|
🌙 Closing as superseded. Issue #18 was resolved and closed by the merged PR #34, which fixes the same No functionality is lost — the precision fix is already on Generated by Claude Code |
Summary
Closes #18.
Three OCPP 1.6J schemas (
SetChargingProfile,RemoteStartTransaction,GetCompositeScheduleresponse) declare"multipleOf": 0.1on charging-ratelimit/minChargingRatefields.serde_jsonrepresents JSON numbers asf64, so21.4_f64 / 0.1_f64 = 213.9999…rather than214.0.jsonschema0.17'sMultipleOfFloatValidatorcomputes(item / multiple_of) % 1.0and only checksremainder < f64::EPSILON, so a semantically-valid 1-decimal value like21.4is 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 withdecimal.Decimal).What was ported
Faithful port of the Python
decimal.Decimalre-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 (only0.1in OCPP 1.6J).normalize_floats(payload, divisors)— for each positivef64x, replaces it withround(x / d) * dwhen|candidate − x| < 1e-9 · max(1, |x|). Empirically(214 * 0.1_f64) / 0.1_f64 == 214.0exactly in IEEE 754, so the normalised value passes jsonschema's% 1.0 < EPSILONcheck. Values that are not near an exact multiple (e.g.21.41→ would round to4.1, delta0.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, andcargo test --workspaceall pass locally (verified on a local merge with currentmain; branch merges cleanly).New tests (5), porting the three Python reference cases plus edge cases:
validate_set_charging_profile_float_limit_passeslimit: 21.4passes (was21.0workaround)validate_get_composite_schedule_response_float_limit_passeslimit: 15.2passes (was15.0)validate_remote_start_transaction_float_limit_passeslimit: 11.5passesvalidate_set_charging_profile_float_min_charging_rate_passesminChargingRate: 6.6passesvalidate_set_charging_profile_two_decimal_limit_failslimit: 21.41still correctly rejectedKnown gaps
multipleOfpayloads are out of scope.SchemaValidatorintoActionDispatcher), which depended on this fix so charging-schedule payloads validate correctly.Python reference
ocpp/messages.py—_validate_payload(), "3 OCPP 1.6 schedules have fields of type floats" commenttests/test_messages.py—test_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