Skip to content

Commit db9f70a

Browse files
committed
imporvise the testcase and improve the return type checker
1 parent dea7c78 commit db9f70a

File tree

5 files changed

+74
-147
lines changed

5 files changed

+74
-147
lines changed

src/dubbo/codec/json_codec/json_codec_handler.py

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
from typing import Any, Optional
17+
from typing import Any, Optional, get_args, get_origin, Union
1818

1919
from ._interfaces import JsonCodec, TypeHandler
2020
from .orjson_codec import OrJsonCodec
@@ -152,40 +152,67 @@ def encode_parameters(self, *arguments) -> bytes:
152152
def decode_return_value(self, data: bytes) -> Any:
153153
"""
154154
Decode return value from JSON bytes and validate against self.return_type.
155+
Supports nested generics and marker-wrapped types.
155156
"""
156-
try:
157-
if not data:
158-
return None
157+
if not data:
158+
return None
159159

160-
# Step 1: Decode JSON bytes into Python objects
161-
json_data = self._decode_with_codecs(data)
160+
# Step 1: Decode JSON bytes to Python object
161+
json_data = self._decode_with_codecs(data)
162162

163-
# Step 2: Reconstruct objects (dataclasses, pydantic, enums, etc.)
164-
obj = self._reconstruct_objects(json_data)
163+
# Step 2: Reconstruct marker-based objects (datetime, UUID, set, frozenset, dataclass, pydantic)
164+
obj = self._reconstruct_objects(json_data)
165165

166-
# Step 3: Strict return type validation
167-
if self.return_type:
168-
from typing import get_origin, get_args, Union
166+
# Step 3: Validate type recursively
167+
if self.return_type:
168+
if not self._validate_type(obj, self.return_type):
169+
raise DeserializationException(
170+
f"Decoded object type {type(obj).__name__} does not match expected {self.return_type}"
171+
)
169172

170-
origin = get_origin(self.return_type)
171-
args = get_args(self.return_type)
173+
return obj
172174

173-
if origin is Union:
174-
if not any(isinstance(obj, arg) for arg in args):
175-
raise DeserializationException(
176-
f"Decoded object type {type(obj).__name__} not in expected Union types {args}"
177-
)
178-
else:
179-
if not isinstance(obj, self.return_type):
180-
raise DeserializationException(
181-
f"Decoded object type {type(obj).__name__} "
182-
f"does not match expected return_type {self.return_type.__name__}"
183-
)
175+
def _validate_type(self, obj: Any, expected_type: type) -> bool:
176+
"""
177+
Recursively validate obj against expected_type.
178+
Supports Union, List, Tuple, Set, frozenset, dataclass, Enum, Pydantic models.
179+
"""
180+
origin = get_origin(expected_type)
181+
args = get_args(expected_type)
184182

185-
return obj
183+
# Handle Union types
184+
if origin is Union:
185+
return any(self._validate_type(obj, t) for t in args)
186186

187-
except Exception as e:
188-
raise DeserializationException(f"Return value decoding failed: {e}") from e
187+
# Handle container types
188+
if origin in (list, tuple, set, frozenset):
189+
if not isinstance(obj, origin):
190+
return False
191+
if args:
192+
return all(self._validate_type(item, args[0]) for item in obj)
193+
return True
194+
195+
# Dataclass
196+
if hasattr(expected_type, "__dataclass_fields__"):
197+
return hasattr(obj, "__dataclass_fields__") and type(obj) == expected_type
198+
199+
# Enum
200+
import enum
201+
202+
if isinstance(expected_type, type) and issubclass(expected_type, enum.Enum):
203+
return isinstance(obj, expected_type)
204+
205+
# Pydantic
206+
try:
207+
from pydantic import BaseModel
208+
209+
if issubclass(expected_type, BaseModel):
210+
return isinstance(obj, expected_type)
211+
except Exception:
212+
pass
213+
214+
# Plain types
215+
return isinstance(obj, expected_type)
189216

190217
# Encoder/Decoder interface compatibility methods
191218
def encoder(self):
@@ -327,18 +354,10 @@ def _reconstruct_objects(self, data: Any) -> Any:
327354
return data
328355

329356
if "$date" in data:
330-
from datetime import datetime
357+
from datetime import datetime, timezone
331358

332-
# Handle both ISO format with and without timezone
333-
date_str = data["$date"]
334-
if date_str.endswith("Z"):
335-
# Remove Z and treat as UTC
336-
date_str = date_str[:-1] + "+00:00"
337-
try:
338-
return datetime.fromisoformat(date_str)
339-
except ValueError:
340-
# Fallback for older formats
341-
return datetime.fromisoformat(date_str.replace("Z", "+00:00"))
359+
dt = datetime.fromisoformat(data["$date"].replace("Z", "+00:00"))
360+
return dt.astimezone(timezone.utc)
342361

343362
elif "$uuid" in data:
344363
from uuid import UUID
@@ -348,6 +367,9 @@ def _reconstruct_objects(self, data: Any) -> Any:
348367
elif "$set" in data:
349368
return set(self._reconstruct_objects(item) for item in data["$set"])
350369

370+
elif "$frozenset" in data:
371+
return frozenset(self._reconstruct_objects(item) for item in data["$frozenset"])
372+
351373
elif "$tuple" in data:
352374
return tuple(self._reconstruct_objects(item) for item in data["$tuple"])
353375

tests/json/json_test.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
from dataclasses import dataclass, asdict
18-
from datetime import date, datetime, time
17+
from dataclasses import asdict, dataclass
18+
from datetime import datetime, timezone
1919
from decimal import Decimal
2020
from enum import Enum
2121
from pathlib import Path
@@ -44,22 +44,20 @@ class Color(Enum):
4444
(12345, int),
4545
(12.34, float),
4646
(True, bool),
47-
(datetime(2025, 8, 27, 13, 0, 0), datetime),
48-
(date(2025, 8, 27), date),
49-
(time(13, 0, 0), time),
47+
(datetime(2025, 8, 27, 13, 0, tzinfo=timezone.utc), datetime),
5048
(Decimal("123.45"), Decimal),
5149
(set([1, 2, 3]), set),
5250
(frozenset(["a", "b"]), frozenset),
5351
(UUID("12345678-1234-5678-1234-567812345678"), UUID),
54-
(Path("/tmp/file.txt"), Path),
5552
(Color.RED, Color),
5653
(SampleDataClass(field1=1, field2="abc"), SampleDataClass),
5754
]
5855

5956

6057
@pytest.mark.parametrize("value,expected_type", test_cases)
6158
def test_json_codec_roundtrip(value, expected_type):
62-
codec = JsonTransportCodec(parameter_types=[type(value)], return_type=type(value))
59+
print(f"Testing value: {value} of type {type(value)}")
60+
codec = JsonTransportCodec(parameter_types=[type(value)], return_type=expected_type)
6361

6462
# Encode
6563
encoded = codec.encode_parameters(value)

tests/json/json_type_test.py

Lines changed: 0 additions & 93 deletions
This file was deleted.

tests/protobuf/generated/greet_pb2.py

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/protobuf/protobuf_test.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717

1818
import pytest
1919

20-
from dubbo.codec.protobuf_codec import ProtobufTransportCodec
21-
from dubbo.codec.protobuf_codec import PrimitiveHandler
22-
from dubbo.codec.protobuf_codec import GoogleProtobufMessageHandler
23-
from dubbo.codec.protobuf_codec.protobuf_codec import SerializationException, DeserializationException
20+
from dubbo.codec.protobuf_codec import GoogleProtobufMessageHandler, PrimitiveHandler, ProtobufTransportCodec
21+
from dubbo.codec.protobuf_codec.protobuf_codec import DeserializationException, SerializationException
2422

2523

2624
def test_primitive_roundtrip_string():
@@ -63,7 +61,7 @@ def test_decode_with_no_return_type_raises():
6361

6462
@pytest.mark.skipif(not GoogleProtobufMessageHandler.__module__, reason="google.protobuf not available")
6563
def test_google_protobuf_roundtrip():
66-
from generated.greet_pb2 import GreeterRequest, GreeterReply
64+
from generated.greet_pb2 import GreeterReply, GreeterRequest
6765

6866
codec = ProtobufTransportCodec(parameter_types=[GreeterRequest], return_type=GreeterReply)
6967

0 commit comments

Comments
 (0)