11from __future__ import annotations
22
3- __all__ = ["ConnectError" ]
3+ __all__ = ["ConnectError" , "ErrorDetail" ]
44
55
6- from typing import TYPE_CHECKING
6+ from typing import TYPE_CHECKING , TypeVar , overload
77
8+ from google .protobuf import symbol_database
89from google .protobuf .any_pb2 import Any
10+ from google .protobuf .message import Message
911
1012if TYPE_CHECKING :
1113 from collections .abc import Iterable , Sequence
1214
13- from google .protobuf .message import Message
14-
1515 from .code import Code
1616
17+ T = TypeVar ("T" , bound = Message )
18+
19+
20+ class ErrorDetail :
21+ """A self-describing Protobuf message attached to a [ConnectError][].
22+
23+ Error details are sent over the network to clients, which can then work with
24+ strongly-typed data rather than trying to parse a complex error message. For
25+ example, you might use details to send a localized error message or retry
26+ parameters to a client.
27+ """
28+
29+ def __init__ (self , message : Message ) -> None :
30+ if isinstance (message , Any ):
31+ self ._message = None
32+ self ._any = message
33+ return
34+ self ._message = message
35+ self ._any = pack_any (message )
36+
37+ @property
38+ def type_name (self ) -> str :
39+ """The fully-qualified name of the details Protobuf message (for example, acme.foo.v1.FooDetail)."""
40+ return self ._any .type_url .removeprefix ("type.googleapis.com/" )
41+
42+ @property
43+ def message_bytes (self ) -> bytes :
44+ """The Protobuf message serialized as bytes."""
45+ return self ._any .value
46+
47+ @overload
48+ def value (self ) -> Message | None : ...
49+
50+ @overload
51+ def value (self , typ : type [T ], / ) -> T | None : ...
52+
53+ def value (self , desc : type [Message ] | None = None ) -> Message | None :
54+ """The details message as a Protobuf message, or None if it cannot be deserialized."""
55+ if self ._message :
56+ return self ._message
57+ if isinstance (desc , type ):
58+ msg = desc ()
59+ if self ._any .Unpack (msg ):
60+ return msg
61+ return None
62+ try :
63+ detail_type = self ._any .type_url .removeprefix ("type.googleapis.com/" )
64+ msg_instance = symbol_database .Default ().GetSymbol (detail_type )()
65+ if self ._any .Unpack (msg_instance ):
66+ return msg_instance
67+ return None
68+ except Exception :
69+ return None
70+
1771
1872class ConnectError (Exception ):
1973 """An exception in a Connect RPC.
@@ -25,7 +79,7 @@ class ConnectError(Exception):
2579 """
2680
2781 def __init__ (
28- self , code : Code , message : str , details : Iterable [Message ] = ()
82+ self , code : Code , message : str , details : Iterable [Message | ErrorDetail ] = ()
2983 ) -> None :
3084 """
3185 Creates a new Connect error.
@@ -40,7 +94,7 @@ def __init__(
4094 self ._message = message
4195
4296 self ._details = (
43- [m if isinstance (m , Any ) else pack_any (m ) for m in details ]
97+ [m if isinstance (m , ErrorDetail ) else ErrorDetail (m ) for m in details ]
4498 if details
4599 else ()
46100 )
@@ -54,7 +108,7 @@ def message(self) -> str:
54108 return self ._message
55109
56110 @property
57- def details (self ) -> Sequence [Any ]:
111+ def details (self ) -> Sequence [ErrorDetail ]:
58112 return self ._details
59113
60114
0 commit comments