Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces BSON data type support for Cloud Firestore MongoDB compatibility workloads, adding classes like BSONObjectID, BSONDecimal128, BSONInt32, BSONRegex, BSONTimestamp, BSONBinary, BSONMinKey, and BSONMaxKey along with their encoding and decoding logic. It also configures default gRPC channel options to allow unlimited message sizes and adds corresponding unit and system tests. The review feedback suggests improving defensive programming by type-validating untrusted dictionary payloads during BSON decoding (per Repository Style Guide Rule 2) and adding range validation during initialization for BSONBinary and BSONInt32 to prevent serialization errors.
| elif "__regex__" in res: | ||
| val = res["__regex__"] | ||
| return BSONRegex(pattern=val["pattern"], flags=val.get("options", "")) | ||
| elif "__request_timestamp__" in res: | ||
| val = res["__request_timestamp__"] | ||
| return BSONTimestamp(seconds=val["seconds"], increment=val["increment"]) |
There was a problem hiding this comment.
According to the Repository Style Guide (Rule 2: Input Validation / Defensive Programming), any data loaded from external sources is untrusted and its structure must be type-validated before indexing or calling dictionary lookup keys to avoid TypeError exceptions.
If res["__regex__"] or res["__request_timestamp__"] is not a dictionary (e.g., if the database payload is malformed), accessing them via subscripting or calling .get() will raise a TypeError or AttributeError.
We should verify that val is a dictionary using isinstance(val, dict) before indexing or calling .get(). If it is not, we can gracefully fall back to returning the original decoded dictionary res.
| elif "__regex__" in res: | |
| val = res["__regex__"] | |
| return BSONRegex(pattern=val["pattern"], flags=val.get("options", "")) | |
| elif "__request_timestamp__" in res: | |
| val = res["__request_timestamp__"] | |
| return BSONTimestamp(seconds=val["seconds"], increment=val["increment"]) | |
| elif "__regex__" in res: | |
| val = res["__regex__"] | |
| if isinstance(val, dict): | |
| return BSONRegex(pattern=val.get("pattern", ""), flags=val.get("options", "")) | |
| elif "__request_timestamp__" in res: | |
| val = res["__request_timestamp__"] | |
| if isinstance(val, dict): | |
| return BSONTimestamp(seconds=val.get("seconds", 0), increment=val.get("increment", 0)) |
References
- Rule 2: Input Validation (Defensive Programming) - Any data loaded from external configuration files/sources is untrusted. Always type-validate structure (e.g. check isinstance(data, dict)) before indexing or calling dictionary lookup keys, avoiding TypeError exceptions. (link)
| def __init__(self, sub_type: int, data: bytes): | ||
| self._sub_type = int(sub_type) | ||
| self._data = bytes(data) |
There was a problem hiding this comment.
The sub_type parameter represents a BSON binary subtype, which must be a single byte (an integer between 0 and 255). If sub_type is outside this range, calling to_map_value() will raise a ValueError: bytes must be in range(0, 256) when attempting bytes([self._sub_type]).
To fail fast and ensure correctness, we should validate that sub_type is within the valid range [0, 255] during initialization.
| def __init__(self, sub_type: int, data: bytes): | |
| self._sub_type = int(sub_type) | |
| self._data = bytes(data) | |
| def __init__(self, sub_type: int, data: bytes): | |
| sub_type_int = int(sub_type) | |
| if not 0 <= sub_type_int <= 255: | |
| raise ValueError("BSONBinary sub_type must be an integer between 0 and 255.") | |
| self._sub_type = sub_type_int | |
| self._data = bytes(data) |
| def __init__(self, value: int): | ||
| self._value = int(value) |
There was a problem hiding this comment.
The BSONInt32 class represents a 32-bit signed integer. However, there is currently no range validation in __init__. If a value outside the 32-bit signed integer range ([-2147483648, 2147483647]) is passed, it will be accepted but may cause serialization errors or silent truncation on the backend.
We should validate that the value fits within the 32-bit signed integer range during initialization.
| def __init__(self, value: int): | |
| self._value = int(value) | |
| def __init__(self, value: int): | |
| val_int = int(value) | |
| if not -2147483648 <= val_int <= 2147483647: | |
| raise ValueError("BSONInt32 value must be a 32-bit signed integer.") | |
| self._value = val_int |
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
Fixes #<issue_number_goes_here> 🦕