2121# DEALINGS IN THE SOFTWARE. =
2222# ==============================================================================
2323from typing import (
24+ Any ,
2425 Dict ,
2526 Generic ,
2627 List ,
2728 NamedTuple ,
29+ Optional ,
2830 Type ,
2931 TypeVar ,
3032 cast ,
3133)
34+ from urllib .parse import quote , unquote
3235
3336from pydantic import TypeAdapter
3437
3538from cloudevents_pydantic .events import CloudEvent
36- from cloudevents_pydantic .formats import json
39+ from cloudevents_pydantic .formats import canonical , json
3740
3841_T = TypeVar ("_T" , bound = CloudEvent )
3942
4043
4144class HTTPComponents (NamedTuple ):
4245 headers : Dict [str , str ]
43- body : str
46+ body : Optional [str ]
47+
48+
49+ _HTTP_safe_chars = "" .join (
50+ [
51+ x
52+ for x in list (map (chr , range (ord ("\u0021 " ), ord ("\u007e " ) + 1 )))
53+ if x not in [" " , '"' , "%" ]
54+ ]
55+ )
56+ """
57+ Characters NOT to be percent encoded in http headers.
58+ https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md#3132-http-header-values
59+ """
4460
4561
4662class HTTPHandler (Generic [_T ]):
@@ -62,7 +78,7 @@ def to_json(self, event: _T) -> HTTPComponents:
6278 :rtype: HTTPComponents
6379 """
6480 headers = {"content-type" : "application/cloudevents+json; charset=UTF-8" }
65- body = json .to_json (event )
81+ body = json .serialize (event )
6682 return HTTPComponents (headers , body )
6783
6884 def to_json_batch (self , events : List [_T ]) -> HTTPComponents :
@@ -75,7 +91,7 @@ def to_json_batch(self, events: List[_T]) -> HTTPComponents:
7591 :rtype: HTTPComponents
7692 """
7793 headers = {"content-type" : "application/cloudevents-batch+json; charset=UTF-8" }
78- body = json .to_json_batch (events , self .batch_adapter )
94+ body = json .serialize_batch (events , self .batch_adapter )
7995 return HTTPComponents (headers , body )
8096
8197 def from_json (
@@ -90,7 +106,7 @@ def from_json(
90106 :return: The deserialized event
91107 :rtype: CloudEvent
92108 """
93- return json .from_json (body , self .event_adapter )
109+ return json .deserialize (body , self .event_adapter )
94110
95111 def from_json_batch (
96112 self ,
@@ -104,4 +120,56 @@ def from_json_batch(
104120 :return: The deserialized event batch
105121 :rtype: List[CloudEvent]
106122 """
107- return json .from_json_batch (body , self .batch_adapter )
123+ return json .deserialize_batch (body , self .batch_adapter )
124+
125+ def to_binary (self , event : _T ) -> HTTPComponents :
126+ """
127+ Serializes an event in HTTP binary format.
128+
129+ :param event: The event object to serialize
130+ :type event: CloudEvent
131+ :return: The headers and the body representation of the event
132+ :rtype: HTTPComponents
133+ """
134+ if event .datacontenttype is None :
135+ raise ValueError ("Can't serialize event without datacontenttype" )
136+
137+ serialized = canonical .serialize (event )
138+
139+ body = serialized .get ("data" )
140+ headers = {
141+ f"ce-{ k } " : self ._header_encode (v )
142+ for k , v in serialized .items ()
143+ if k not in ["data" , "datacontenttype" ] and v is not None
144+ }
145+ headers ["content-type" ] = self ._header_encode (serialized ["datacontenttype" ])
146+
147+ return HTTPComponents (headers , body )
148+
149+ def from_binary (self , headers : Dict [str , str ], body : Any ) -> CloudEvent :
150+ """
151+ Deserializes an event from HTTP binary format.
152+
153+ :param headers: The request headers
154+ :type headers: Dict[str, str]
155+ :param body: The request body
156+ :type body: Any
157+ :return:
158+ """
159+ if not headers .get ("content-type" ):
160+ raise ValueError ("content-type not found in headers" )
161+
162+ canonical_data = {
163+ k [3 :]: self ._header_decode (v )
164+ for k , v in headers .items ()
165+ if k .startswith ("ce-" )
166+ }
167+ canonical_data ["datacontenttype" ] = self ._header_decode (headers ["content-type" ])
168+ canonical_data ["data" ] = body
169+ return canonical .deserialize (canonical_data , self .event_adapter )
170+
171+ def _header_encode (self , value : str ) -> str :
172+ return quote (value , safe = _HTTP_safe_chars )
173+
174+ def _header_decode (self , value : str ) -> str :
175+ return unquote (value , errors = "strict" )
0 commit comments