fix(validation): enforce strict CDR types and storage ranges
This commit is contained in:
49
app/common/validation.py
Normal file
49
app/common/validation.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""Shared wire schema. Semantic storage validation belongs to the worker."""
|
||||
import json
|
||||
import math
|
||||
|
||||
STRING_FIELDS = ("raw_id", "device_id", "caller_num", "svc", "lac", "ci")
|
||||
INTEGER_FIELDS = ("rat", "epoch_start", "epoch_end", "up_bytes", "down_bytes")
|
||||
REQUIRED_FIELDS = STRING_FIELDS + INTEGER_FIELDS
|
||||
|
||||
|
||||
class PayloadError(ValueError):
|
||||
def __init__(self, reason, detail, field=None):
|
||||
super().__init__(detail)
|
||||
self.reason = reason
|
||||
self.field = field
|
||||
|
||||
|
||||
def parse_payload(data):
|
||||
def reject_constant(value):
|
||||
raise ValueError(f"Non-finite number: {value}")
|
||||
def finite_float(value):
|
||||
number = float(value)
|
||||
if not math.isfinite(number):
|
||||
raise ValueError(f"Non-finite number: {value}")
|
||||
return number
|
||||
try:
|
||||
payload = json.loads(data.decode("utf-8"), parse_constant=reject_constant, parse_float=finite_float)
|
||||
except (UnicodeError, ValueError, RecursionError) as exc:
|
||||
raise PayloadError("invalid_json", str(exc)) from exc
|
||||
if not isinstance(payload, dict):
|
||||
raise PayloadError("invalid_payload", "Expected a JSON object")
|
||||
return payload
|
||||
|
||||
|
||||
def validate_structure(payload):
|
||||
for field in REQUIRED_FIELDS:
|
||||
if payload.get(field) is None:
|
||||
raise PayloadError("missing_required_field", f"Field {field} is required", field)
|
||||
for field in STRING_FIELDS:
|
||||
if not isinstance(payload[field], str) or not payload[field].strip():
|
||||
raise PayloadError("invalid_payload", f"Field {field} must be a nonempty string", field)
|
||||
try:
|
||||
payload[field].encode("utf-8")
|
||||
except UnicodeError as exc:
|
||||
raise PayloadError("invalid_payload", f"Field {field} contains invalid Unicode", field) from exc
|
||||
for field in INTEGER_FIELDS:
|
||||
if type(payload[field]) is not int:
|
||||
raise PayloadError("invalid_payload", f"Field {field} must be an integer", field)
|
||||
if payload.get("src_ip") is not None and not isinstance(payload["src_ip"], str):
|
||||
raise PayloadError("invalid_payload", "Field src_ip must be a string or null", "src_ip")
|
||||
Reference in New Issue
Block a user