11package cloudevent
22
33import (
4+ "encoding/base64"
45 "encoding/json"
6+ "fmt"
57 "reflect"
68 "strings"
79
810 "github.com/tidwall/sjson"
911)
1012
11- var definedCloudeEventHdrFields = getJSONFieldNames (reflect .TypeOf ( CloudEventHeader {} ))
13+ var definedCloudeEventHdrFields = getJSONFieldNames (reflect .TypeFor [ CloudEventHeader ]( ))
1214
1315type cloudEventHeader CloudEventHeader
1416
1517// UnmarshalJSON implements custom JSON unmarshaling for CloudEvent.
18+ // It transparently handles both "data" and "data_base64" wire formats.
19+ // For RawEvent (CloudEvent[json.RawMessage]), Data is set to the raw payload bytes.
1620func (c * CloudEvent [A ]) UnmarshalJSON (data []byte ) error {
17- var err error
18- c .CloudEventHeader , err = unmarshalCloudEvent (data , c .setDataField )
19- return err
21+ var dataRaw json.RawMessage
22+ var dataBase64 string
23+ header , err := unmarshalCloudEventWithPayload (data , func (d json.RawMessage , b64 string ) error {
24+ dataRaw = d
25+ dataBase64 = b64
26+ return nil
27+ })
28+ if err != nil {
29+ return err
30+ }
31+ c .CloudEventHeader = header
32+ if dataRaw != nil && dataBase64 != "" {
33+ return fmt .Errorf ("cloudevent: both \" data\" and \" data_base64\" present; only one allowed" )
34+ }
35+ // RawEvent: assign payload bytes directly to Data
36+ if ptr , ok := (any )(& c .Data ).(* json.RawMessage ); ok {
37+ if dataBase64 != "" {
38+ decoded , err := base64 .StdEncoding .DecodeString (dataBase64 )
39+ if err != nil {
40+ return err
41+ }
42+ * ptr = decoded
43+ c .DataBase64 = dataBase64
44+ } else {
45+ * ptr = dataRaw
46+ c .DataBase64 = ""
47+ }
48+ return nil
49+ }
50+ // Typed CloudEvent: unmarshal into Data
51+ if dataBase64 != "" {
52+ decoded , err := base64 .StdEncoding .DecodeString (dataBase64 )
53+ if err != nil {
54+ return err
55+ }
56+ if err := json .Unmarshal (decoded , & c .Data ); err != nil {
57+ return err
58+ }
59+ c .DataBase64 = dataBase64
60+ } else if dataRaw != nil {
61+ if err := json .Unmarshal (dataRaw , & c .Data ); err != nil {
62+ return err
63+ }
64+ }
65+ return nil
2066}
2167
22- // MarshalJSON implements custom JSON marshaling for CloudEventHeader.
68+ // MarshalJSON implements custom JSON marshaling for CloudEvent[A].
69+ // When DataBase64 is set, emits "data_base64"; otherwise emits "data".
70+ // For RawEvent, uses DataContentType to choose wire form when DataBase64 is empty.
2371func (c CloudEvent [A ]) MarshalJSON () ([]byte , error ) {
24- // Marshal the base struct
2572 data , err := json .Marshal (c .CloudEventHeader )
2673 if err != nil {
2774 return nil , err
2875 }
29- data , err = sjson .SetBytes (data , "data" , c .Data )
76+ // RawEvent: choose data vs data_base64 by content type when DataBase64 not set
77+ if raw , ok := (any )(c .Data ).(json.RawMessage ); ok {
78+ if len (raw ) > 0 || c .DataBase64 != "" {
79+ if c .DataBase64 != "" {
80+ data , err = sjson .SetBytes (data , "data_base64" , c .DataBase64 )
81+ } else if IsJSONDataContentType (c .DataContentType ) || (c .DataContentType == "" && json .Valid (raw )) {
82+ data , err = sjson .SetRawBytes (data , "data" , raw )
83+ } else {
84+ data , err = sjson .SetBytes (data , "data_base64" , base64 .StdEncoding .EncodeToString (raw ))
85+ }
86+ if err != nil {
87+ return nil , err
88+ }
89+ }
90+ return data , nil
91+ }
92+ if c .DataBase64 != "" {
93+ data , err = sjson .SetBytes (data , "data_base64" , c .DataBase64 )
94+ } else {
95+ data , err = sjson .SetBytes (data , "data" , c .Data )
96+ }
3097 if err != nil {
3198 return nil , err
3299 }
@@ -42,21 +109,18 @@ func (c *CloudEventHeader) UnmarshalJSON(data []byte) error {
42109
43110// MarshalJSON implements custom JSON marshaling for CloudEventHeader.
44111func (c CloudEventHeader ) MarshalJSON () ([]byte , error ) {
45- // Marshal the base struct
46112 aux := (cloudEventHeader )(c )
47113 aux .SpecVersion = SpecVersion
48114 data , err := json .Marshal (aux )
49115 if err != nil {
50116 return nil , err
51117 }
52- // Add all extras using sjson]
53118 for k , v := range c .Extras {
54119 data , err = sjson .SetBytes (data , k , v )
55120 if err != nil {
56121 return nil , err
57122 }
58123 }
59-
60124 return data , nil
61125}
62126
@@ -90,50 +154,105 @@ func getJSONFieldNames(t reflect.Type) map[string]struct{} {
90154
91155// unmarshalCloudEvent unmarshals the CloudEventHeader and data field.
92156func unmarshalCloudEvent (data []byte , dataFunc func (json.RawMessage ) error ) (CloudEventHeader , error ) {
93- c := CloudEventHeader {}
94- aux := cloudEventHeader {}
95- // Unmarshal known fields directly into the struct
96- if err := json . Unmarshal ( data , & aux ); err != nil {
97- return c , err
98- }
99- aux . SpecVersion = SpecVersion
100- c = ( CloudEventHeader )( aux )
101- // Create a map to hold all JSON fields
157+ return unmarshalCloudEventWithPayload ( data , func ( dataRaw json. RawMessage , _ string ) error {
158+ return dataFunc ( dataRaw )
159+ })
160+ }
161+
162+ // unmarshalCloudEventWithPayload unmarshals the CloudEventHeader and returns both
163+ // "data" and "data_base64" for CloudEvent payload. Single pass: parse once into
164+ // map[string]json.RawMessage, then fill header and payload from the map.
165+ func unmarshalCloudEventWithPayload ( data [] byte , payloadFunc func ( dataRaw json. RawMessage , dataBase64 string ) error ) ( CloudEventHeader , error ) {
102166 rawFields := make (map [string ]json.RawMessage )
103167 if err := json .Unmarshal (data , & rawFields ); err != nil {
104- return c , err
168+ return CloudEventHeader {} , err
105169 }
106170
107- // Separate known and unknown fields
108- for key , rawValue := range rawFields {
109- if _ , ok := definedCloudeEventHdrFields [key ]; ok {
110- // Skip defined fields
171+ var c CloudEventHeader
172+ for key , raw := range rawFields {
173+ if key == "data" || key == "data_base64" {
111174 continue
112175 }
113- if key == "data" {
114- if err := dataFunc ( rawValue ); err != nil {
176+ if _ , defined := definedCloudeEventHdrFields [ key ]; defined {
177+ if err := setHeaderField ( & c , key , raw ); err != nil {
115178 return c , err
116179 }
117- continue
180+ } else {
181+ if c .Extras == nil {
182+ c .Extras = make (map [string ]any )
183+ }
184+ var value any
185+ if err := json .Unmarshal (raw , & value ); err != nil {
186+ return c , err
187+ }
188+ c .Extras [key ] = value
118189 }
119- if c .Extras == nil {
120- c .Extras = make (map [string ]any )
190+ }
191+ c .SpecVersion = SpecVersion
192+
193+ var dataRaw json.RawMessage
194+ var dataBase64 string
195+ if raw , ok := rawFields ["data_base64" ]; ok && len (raw ) > 0 {
196+ if err := json .Unmarshal (raw , & dataBase64 ); err != nil {
197+ return c , err
121198 }
122- var value any
123- if err := json .Unmarshal (rawValue , & value ); err != nil {
199+ }
200+ if raw , ok := rawFields ["data" ]; ok {
201+ dataRaw = raw
202+ }
203+ if dataRaw != nil || dataBase64 != "" {
204+ if err := payloadFunc (dataRaw , dataBase64 ); err != nil {
124205 return c , err
125206 }
126- c .Extras [key ] = value
127207 }
128208 return c , nil
129209}
130210
211+ // setHeaderField unmarshals raw into the CloudEventHeader field for key.
212+ func setHeaderField (c * CloudEventHeader , key string , raw json.RawMessage ) error {
213+ switch key {
214+ case "id" :
215+ return json .Unmarshal (raw , & c .ID )
216+ case "source" :
217+ return json .Unmarshal (raw , & c .Source )
218+ case "producer" :
219+ return json .Unmarshal (raw , & c .Producer )
220+ case "specversion" :
221+ return json .Unmarshal (raw , & c .SpecVersion )
222+ case "subject" :
223+ return json .Unmarshal (raw , & c .Subject )
224+ case "time" :
225+ return json .Unmarshal (raw , & c .Time )
226+ case "type" :
227+ return json .Unmarshal (raw , & c .Type )
228+ case "datacontenttype" :
229+ return json .Unmarshal (raw , & c .DataContentType )
230+ case "dataschema" :
231+ return json .Unmarshal (raw , & c .DataSchema )
232+ case "dataversion" :
233+ return json .Unmarshal (raw , & c .DataVersion )
234+ case "signature" :
235+ return json .Unmarshal (raw , & c .Signature )
236+ case "tags" :
237+ return json .Unmarshal (raw , & c .Tags )
238+ default :
239+ return nil
240+ }
241+ }
242+
131243// ignoreDataField is a function that ignores the data field.
132244// It is used when unmarshalling the CloudEventHeader so that the data field is not added to the Extras map.
133245func ignoreDataField (json.RawMessage ) error { return nil }
134246
135- // setDataField is a function that sets the data field.
136- // It is used to unmarshal the data field into the CloudEvent[A].Data field.
137- func (c * CloudEvent [A ]) setDataField (data json.RawMessage ) error {
138- return json .Unmarshal (data , & c .Data )
247+ // DecodeHeader parses only the CloudEvent header fields from JSON, skipping the data payload.
248+ // More efficient than unmarshaling a full event when only metadata is needed (e.g. for scaling).
249+ //
250+ // Equivalent to json.Unmarshal(data, &header) since CloudEventHeader.UnmarshalJSON
251+ // already ignores the data field, but provided as an explicit API for discoverability.
252+ func DecodeHeader (data []byte ) (CloudEventHeader , error ) {
253+ var hdr CloudEventHeader
254+ if err := json .Unmarshal (data , & hdr ); err != nil {
255+ return CloudEventHeader {}, err
256+ }
257+ return hdr , nil
139258}
0 commit comments