Skip to content

Commit 6d2d29d

Browse files
authored
Fix validation of payloads containing floats
The validation of payloads using jsonschemas could fail when the payload contained a float. This problem is described in this issue: python-jsonschema/jsonschema#247 This commit implements a work around for this issue by changing the float parser for certain payloads from `float()` to `decimal.Decimal()`. Fixes: #43
1 parent 5b70a4f commit 6d2d29d

File tree

2 files changed

+72
-13
lines changed

2 files changed

+72
-13
lines changed

ocpp/messages.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
also contain some helper functions for packing and unpacking messages. """
33
import os
44
import json
5+
import decimal
56
from dataclasses import asdict, is_dataclass
67

78
from jsonschema import validate
@@ -61,11 +62,14 @@ def pack(msg):
6162
return msg.to_json()
6263

6364

64-
def get_schema(message_type_id, action, ocpp_version):
65+
def get_schema(message_type_id, action, ocpp_version, parse_float=float):
6566
"""
6667
Read schema from disk and return in. Reads will be cached for performance
6768
reasons.
6869
70+
The `parse_float` argument can be used to set the conversion method that
71+
is used to parse floats. It must be a callable taking 1 argument. By
72+
default it is `float()`, but certain schema's require `decimal.Decimal()`.
6973
"""
7074
if ocpp_version not in ["1.6", "2.0"]:
7175
raise ValueError
@@ -95,7 +99,7 @@ def get_schema(message_type_id, action, ocpp_version):
9599
# Unexpected UTF-8 BOM (decode using utf-8-sig):
96100
with open(path, 'r', encoding='utf-8-sig') as f:
97101
data = f.read()
98-
_schemas[relative_path] = json.loads(data)
102+
_schemas[relative_path] = json.loads(data, parse_float=parse_float)
99103

100104
return _schemas[relative_path]
101105

@@ -108,21 +112,44 @@ def validate_payload(message, ocpp_version):
108112
"be either 'Call' or 'CallResult'.")
109113

110114
try:
111-
schema = get_schema(
112-
message.message_type_id, message.action, ocpp_version
113-
)
115+
# 3 OCPP 1.6 schedules have fields of type floats. The JSON schema
116+
# defines a certain precision for these fields of 1 decimal. A value of
117+
# 21.4 is valid, whereas a value if 4.11 is not.
118+
#
119+
# The problem is that Python's internal representation of 21.4 might
120+
# have more than 1 decimal. It might be 21.399999999999995. This would
121+
# make the validation fail, although the payload is correct. This is a
122+
# known issue with jsonschemas, see:
123+
# https://github.com/Julian/jsonschema/issues/247
124+
#
125+
# This issue can be fixed by using a different parser for floats than
126+
# the default one that is used.
127+
#
128+
# Both the schema and the payload must be parsed using the different
129+
# parser for floats.
130+
if ocpp_version == '1.6' and (
131+
(type(message) == Call and
132+
message.action in ['SetChargingProfile', 'RemoteStartTransaction']) # noqa
133+
or
134+
(type(message) == CallResult and
135+
message.action == ['GetCompositeSchedule'])
136+
):
137+
schema = get_schema(
138+
message.message_type_id, message.action,
139+
ocpp_version, parse_float=decimal.Decimal
140+
)
141+
142+
message.payload = json.loads(
143+
json.dumps(message.payload), parse_float=decimal.Decimal
144+
)
145+
else:
146+
schema = get_schema(
147+
message.message_type_id, message.action, ocpp_version
148+
)
114149
except (OSError, json.JSONDecodeError) as e:
115150
raise ValidationError("Failed to load validation schema for action "
116151
f"'{message.action}': {e}")
117152

118-
if message.action in [
119-
'RemoteStartTransaction',
120-
'SetChargingProfile',
121-
'RequestStartTransaction',
122-
]:
123-
# todo: special actions
124-
pass
125-
126153
try:
127154
validate(message.payload, schema)
128155
except SchemaValidationError as e:

tests/test_messages.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,38 @@ def test_get_schema_with_valid_name():
7676
}
7777

7878

79+
def test_validate_set_charging_profile_payload():
80+
"""" Test if payloads with floats are validated correctly.
81+
82+
This test uses the value of 21.4, which is internally represented as
83+
21.39999999999999857891452847979962825775146484375.
84+
You can verify this using `decimal.Decimal(21.4)`
85+
"""
86+
message = Call(
87+
unique_id="1234",
88+
action="SetChargingProfile",
89+
payload={
90+
'connectorId': 1,
91+
'csChargingProfiles': {
92+
'chargingProfileId': 1,
93+
'stackLevel': 0,
94+
'chargingProfilePurpose': 'TxProfile',
95+
'chargingProfileKind': 'Relative',
96+
'chargingSchedule': {
97+
'chargingRateUnit': 'A',
98+
'chargingSchedulePeriod': [{
99+
'startPeriod': 0,
100+
'limit': 21.4
101+
}]
102+
},
103+
'transactionId': 123456789,
104+
}
105+
}
106+
)
107+
108+
validate_payload(message, ocpp_version="1.6")
109+
110+
79111
def test_get_schema_with_invalid_name():
80112
"""
81113
Test if OSError is raised when schema validation file cannnot be found.

0 commit comments

Comments
 (0)