cloud_events 0.3.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +12 -10
- data/lib/cloud_events/content_type.rb +6 -4
- data/lib/cloud_events/errors.rb +37 -5
- data/lib/cloud_events/event/field_interpreter.rb +3 -3
- data/lib/cloud_events/event/opaque.rb +80 -0
- data/lib/cloud_events/event/v0.rb +10 -5
- data/lib/cloud_events/event/v1.rb +144 -21
- data/lib/cloud_events/event.rb +1 -1
- data/lib/cloud_events/format.rb +277 -0
- data/lib/cloud_events/http_binding.rb +335 -150
- data/lib/cloud_events/json_format.rb +232 -61
- data/lib/cloud_events/text_format.rb +73 -0
- data/lib/cloud_events/version.rb +1 -1
- data/lib/cloud_events.rb +2 -0
- metadata +7 -4
@@ -15,14 +15,24 @@ module CloudEvents
|
|
15
15
|
#
|
16
16
|
class HttpBinding
|
17
17
|
##
|
18
|
-
#
|
18
|
+
# The name of the JSON decoder/encoder
|
19
|
+
# @return [String]
|
20
|
+
#
|
21
|
+
JSON_FORMAT = "json"
|
22
|
+
|
23
|
+
# @private
|
24
|
+
ILLEGAL_METHODS = ["GET", "HEAD"].freeze
|
25
|
+
|
26
|
+
##
|
27
|
+
# Returns a default HTTP binding, including support for JSON format.
|
28
|
+
#
|
29
|
+
# @return [HttpBinding]
|
19
30
|
#
|
20
31
|
def self.default
|
21
32
|
@default ||= begin
|
22
33
|
http_binding = new
|
23
|
-
|
24
|
-
http_binding.
|
25
|
-
http_binding.register_batched_formatter "json", json_format
|
34
|
+
http_binding.register_formatter JsonFormat.new, encoder_name: JSON_FORMAT
|
35
|
+
http_binding.default_encoder_name = JSON_FORMAT
|
26
36
|
http_binding
|
27
37
|
end
|
28
38
|
end
|
@@ -31,229 +41,290 @@ module CloudEvents
|
|
31
41
|
# Create an empty HTTP binding.
|
32
42
|
#
|
33
43
|
def initialize
|
34
|
-
@
|
35
|
-
|
44
|
+
@event_decoders = Format::Multi.new do |result|
|
45
|
+
result&.key?(:event) || result&.key?(:event_batch) ? result : nil
|
46
|
+
end
|
47
|
+
@event_encoders = {}
|
48
|
+
@data_decoders = Format::Multi.new do |result|
|
49
|
+
result&.key?(:data) && result&.key?(:content_type) ? result : nil
|
50
|
+
end
|
51
|
+
@data_encoders = Format::Multi.new do |result|
|
52
|
+
result&.key?(:content) && result&.key?(:content_type) ? result : nil
|
53
|
+
end
|
54
|
+
text_format = TextFormat.new
|
55
|
+
@data_decoders.formats.replace [text_format, DefaultDataFormat]
|
56
|
+
@data_encoders.formats.replace [text_format, DefaultDataFormat]
|
57
|
+
|
58
|
+
@default_encoder_name = nil
|
36
59
|
end
|
37
60
|
|
38
61
|
##
|
39
|
-
# Register a formatter for
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# {CloudEvents::JsonFormat} for an example.
|
62
|
+
# Register a formatter for all operations it supports, based on which
|
63
|
+
# methods are implemented by the formatter object. See
|
64
|
+
# {CloudEvents::Format} for a list of possible methods.
|
43
65
|
#
|
44
|
-
# @param
|
45
|
-
#
|
46
|
-
#
|
66
|
+
# @param formatter [Object] The formatter
|
67
|
+
# @param encoder_name [String] The encoder name under which this formatter
|
68
|
+
# will register its encode operations. Optional. If not specified, any
|
69
|
+
# event encoder will _not_ be registered.
|
70
|
+
# @param deprecated_name [String] This positional argument is deprecated
|
71
|
+
# and will be removed in version 1.0. Use encoder_name instead.
|
47
72
|
# @return [self]
|
48
73
|
#
|
49
|
-
def
|
50
|
-
|
51
|
-
|
74
|
+
def register_formatter formatter, deprecated_name = nil, encoder_name: nil
|
75
|
+
encoder_name ||= deprecated_name
|
76
|
+
encoder_name = encoder_name.to_s.strip.downcase if encoder_name
|
77
|
+
decode_event = formatter.respond_to? :decode_event
|
78
|
+
encode_event = encoder_name if formatter.respond_to? :encode_event
|
79
|
+
decode_data = formatter.respond_to? :decode_data
|
80
|
+
encode_data = formatter.respond_to? :encode_data
|
81
|
+
register_formatter_methods formatter,
|
82
|
+
decode_event: decode_event,
|
83
|
+
encode_event: encode_event,
|
84
|
+
decode_data: decode_data,
|
85
|
+
encode_data: encode_data
|
52
86
|
self
|
53
87
|
end
|
54
88
|
|
55
89
|
##
|
56
|
-
#
|
90
|
+
# Registers the given formatter for the given operations. Some arguments
|
91
|
+
# are activated by passing `true`, whereas those that rely on a format name
|
92
|
+
# are activated by passing in a name string.
|
57
93
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# @param
|
62
|
-
#
|
63
|
-
# @param
|
94
|
+
# @param formatter [Object] The formatter
|
95
|
+
# @param decode_event [boolean] If true, register the formatter's
|
96
|
+
# {CloudEvents::Format#decode_event} method.
|
97
|
+
# @param encode_event [String] If set to a string, use the formatter's
|
98
|
+
# {CloudEvents::Format#encode_event} method when that name is requested.
|
99
|
+
# @param decode_data [boolean] If true, register the formatter's
|
100
|
+
# {CloudEvents::Format#decode_data} method.
|
101
|
+
# @param encode_data [boolean] If true, register the formatter's
|
102
|
+
# {CloudEvents::Format#encode_data} method.
|
64
103
|
# @return [self]
|
65
104
|
#
|
66
|
-
def
|
67
|
-
|
68
|
-
|
105
|
+
def register_formatter_methods formatter,
|
106
|
+
decode_event: false,
|
107
|
+
encode_event: nil,
|
108
|
+
decode_data: false,
|
109
|
+
encode_data: false
|
110
|
+
@event_decoders.formats.unshift formatter if decode_event
|
111
|
+
if encode_event
|
112
|
+
encoders = @event_encoders[encode_event] ||= Format::Multi.new do |result|
|
113
|
+
result&.key?(:content) && result&.key?(:content_type) ? result : nil
|
114
|
+
end
|
115
|
+
encoders.formats.unshift formatter
|
116
|
+
end
|
117
|
+
@data_decoders.formats.unshift formatter if decode_data
|
118
|
+
@data_encoders.formats.unshift formatter if encode_data
|
69
119
|
self
|
70
120
|
end
|
71
121
|
|
122
|
+
##
|
123
|
+
# The name of the encoder to use if none is specified
|
124
|
+
#
|
125
|
+
# @return [String,nil]
|
126
|
+
#
|
127
|
+
attr_accessor :default_encoder_name
|
128
|
+
|
129
|
+
##
|
130
|
+
# Analyze a Rack environment hash and determine whether it is _probably_
|
131
|
+
# a CloudEvent. This is done by examining headers only, and does not read
|
132
|
+
# or parse the request body. The result is a best guess: false negatives or
|
133
|
+
# false positives are possible for edge cases, but the logic should
|
134
|
+
# generally detect canonically-formatted events.
|
135
|
+
#
|
136
|
+
# @param env [Hash] The Rack environment.
|
137
|
+
# @return [boolean] Whether the request is likely a CloudEvent.
|
138
|
+
#
|
139
|
+
def probable_event? env
|
140
|
+
return false if ILLEGAL_METHODS.include? env["REQUEST_METHOD"]
|
141
|
+
return true if env["HTTP_CE_SPECVERSION"]
|
142
|
+
content_type = ContentType.new env["CONTENT_TYPE"].to_s
|
143
|
+
content_type.media_type == "application" &&
|
144
|
+
["cloudevents", "cloudevents-batch"].include?(content_type.subtype_base)
|
145
|
+
end
|
146
|
+
|
72
147
|
##
|
73
148
|
# Decode an event from the given Rack environment hash. Following the
|
74
149
|
# CloudEvents spec, this chooses a handler based on the Content-Type of
|
75
150
|
# the request.
|
76
151
|
#
|
152
|
+
# Note that this method will read the body (i.e. `rack.input`) stream.
|
153
|
+
# If you need to access the body after calling this method, you will need
|
154
|
+
# to rewind the stream. To determine whether the request is a CloudEvent
|
155
|
+
# without reading the body, use {#probable_event?}.
|
156
|
+
#
|
77
157
|
# @param env [Hash] The Rack environment.
|
158
|
+
# @param allow_opaque [boolean] If true, returns opaque event objects if
|
159
|
+
# the input is not in a recognized format. If false, raises
|
160
|
+
# {CloudEvents::UnsupportedFormatError} in that case. Default is false.
|
78
161
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
79
162
|
# @return [CloudEvents::Event] if the request includes a single structured
|
80
163
|
# or binary event.
|
81
164
|
# @return [Array<CloudEvents::Event>] if the request includes a batch of
|
82
165
|
# structured events.
|
83
|
-
# @
|
166
|
+
# @raise [CloudEvents::CloudEventsError] if an event could not be decoded
|
167
|
+
# from the request.
|
84
168
|
#
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
return decode_batched_content content, content_type.subtype_format, **format_args
|
97
|
-
end
|
169
|
+
def decode_event env, allow_opaque: false, **format_args
|
170
|
+
request_method = env["REQUEST_METHOD"]
|
171
|
+
raise NotCloudEventError, "Request method is #{request_method}" if ILLEGAL_METHODS.include? request_method
|
172
|
+
content_type_string = env["CONTENT_TYPE"]
|
173
|
+
content_type = ContentType.new content_type_string if content_type_string
|
174
|
+
content = read_with_charset env["rack.input"], content_type&.charset
|
175
|
+
result = decode_binary_content(content, content_type, env, false, **format_args) ||
|
176
|
+
decode_structured_content(content, content_type, allow_opaque, **format_args)
|
177
|
+
if result.nil?
|
178
|
+
content_type_string = content_type_string ? content_type_string.inspect : "not present"
|
179
|
+
raise NotCloudEventError, "Content-Type is #{content_type_string}, and CE-SpecVersion is not present"
|
98
180
|
end
|
99
|
-
|
181
|
+
result
|
100
182
|
end
|
101
183
|
|
102
184
|
##
|
103
|
-
#
|
104
|
-
# passed the request body, if the Content-Type is of the form
|
105
|
-
# `application/cloudevents+format`.
|
185
|
+
# Encode an event or batch of events into HTTP headers and body.
|
106
186
|
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# @param format_args [keywords] Extra args to pass to the formatter.
|
110
|
-
# @return [CloudEvents::Event]
|
187
|
+
# You may provide an event, an array of events, or an opaque event. You may
|
188
|
+
# also specify what content mode and format to use.
|
111
189
|
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
##
|
122
|
-
# Decode a batch of events from the given content data. This should be
|
123
|
-
# passed the request body, if the Content-Type is of the form
|
124
|
-
# `application/cloudevents-batch+format`.
|
190
|
+
# The result is a two-element array where the first element is a headers
|
191
|
+
# list (as defined in the Rack specification) and the second is a string
|
192
|
+
# containing the HTTP body content. When using structured content mode, the
|
193
|
+
# headers list will contain only a `Content-Type` header and the body will
|
194
|
+
# contain the serialized event. When using binary mode, the header list
|
195
|
+
# will contain the serialized event attributes and the body will contain
|
196
|
+
# the serialized event data.
|
125
197
|
#
|
126
|
-
# @param
|
127
|
-
#
|
198
|
+
# @param event [CloudEvents::Event,Array<CloudEvents::Event>,CloudEvents::Event::Opaque]
|
199
|
+
# The event, batch, or opaque event.
|
200
|
+
# @param structured_format [boolean,String] If given, the data will be
|
201
|
+
# encoded in structured content mode. You can pass a string to select
|
202
|
+
# a format name, or pass `true` to use the default format. If set to
|
203
|
+
# `false` (the default), the data will be encoded in binary mode.
|
128
204
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
129
|
-
# @return [Array
|
205
|
+
# @return [Array(headers,String)]
|
130
206
|
#
|
131
|
-
def
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
207
|
+
def encode_event event, structured_format: false, **format_args
|
208
|
+
if event.is_a? Event::Opaque
|
209
|
+
[{ "Content-Type" => event.content_type.to_s }, event.content]
|
210
|
+
elsif !structured_format
|
211
|
+
if event.is_a? ::Array
|
212
|
+
raise ::ArgumentError, "Encoding a batch requires structured_format"
|
213
|
+
end
|
214
|
+
encode_binary_content event, legacy_data_encode: false, **format_args
|
215
|
+
else
|
216
|
+
structured_format = default_encoder_name if structured_format == true
|
217
|
+
raise ::ArgumentError, "Format name not specified, and no default is set" unless structured_format
|
218
|
+
case event
|
219
|
+
when ::Array
|
220
|
+
encode_batched_content event, structured_format, **format_args
|
221
|
+
when Event
|
222
|
+
encode_structured_content event, structured_format, **format_args
|
223
|
+
else
|
224
|
+
raise ::ArgumentError, "Unknown event type: #{event.class}"
|
225
|
+
end
|
136
226
|
end
|
137
|
-
raise HttpContentError, "Unknown cloudevents batch format: #{format.inspect}"
|
138
227
|
end
|
139
228
|
|
140
229
|
##
|
141
|
-
# Decode an event from the given Rack environment
|
230
|
+
# Decode an event from the given Rack environment hash. Following the
|
231
|
+
# CloudEvents spec, this chooses a handler based on the Content-Type of
|
232
|
+
# the request.
|
142
233
|
#
|
143
|
-
# @
|
144
|
-
# @param content_type [CloudEvents::ContentType] the content type from the
|
145
|
-
# Rack environment.
|
146
|
-
# @return [CloudEvents::Event] if a CloudEvent could be decoded from the
|
147
|
-
# Rack environment.
|
148
|
-
# @return [nil] if the Rack environment does not indicate a CloudEvent
|
234
|
+
# @deprecated Will be removed in version 1.0. Use {#decode_event} instead.
|
149
235
|
#
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
236
|
+
# @param env [Hash] The Rack environment.
|
237
|
+
# @param format_args [keywords] Extra args to pass to the formatter.
|
238
|
+
# @return [CloudEvents::Event] if the request includes a single structured
|
239
|
+
# or binary event.
|
240
|
+
# @return [Array<CloudEvents::Event>] if the request includes a batch of
|
241
|
+
# structured events.
|
242
|
+
# @return [nil] if the request does not appear to be a CloudEvent.
|
243
|
+
# @raise [CloudEvents::CloudEventsError] if the request appears to be a
|
244
|
+
# CloudEvent but decoding failed.
|
245
|
+
#
|
246
|
+
def decode_rack_env env, **format_args
|
247
|
+
content_type_string = env["CONTENT_TYPE"]
|
248
|
+
content_type = ContentType.new content_type_string if content_type_string
|
249
|
+
content = read_with_charset env["rack.input"], content_type&.charset
|
250
|
+
env["rack.input"].rewind rescue nil
|
251
|
+
decode_binary_content(content, content_type, env, true, **format_args) ||
|
252
|
+
decode_structured_content(content, content_type, false, **format_args)
|
166
253
|
end
|
167
254
|
|
168
255
|
##
|
169
|
-
# Encode a single event
|
256
|
+
# Encode a single event in structured content mode in the given format.
|
170
257
|
#
|
171
|
-
#
|
172
|
-
# list (as defined in the Rack specification) and the second is a string
|
173
|
-
# containing the HTTP body content. The headers list will contain only
|
174
|
-
# one header, a `Content-Type` whose value is of the form
|
175
|
-
# `application/cloudevents+format`.
|
258
|
+
# @deprecated Will be removed in version 1.0. Use {#encode_event} instead.
|
176
259
|
#
|
177
260
|
# @param event [CloudEvents::Event] The event.
|
178
|
-
# @param
|
261
|
+
# @param format_name [String] The format name.
|
179
262
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
180
263
|
# @return [Array(headers,String)]
|
181
264
|
#
|
182
|
-
def encode_structured_content event,
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
|
265
|
+
def encode_structured_content event, format_name, **format_args
|
266
|
+
result = @event_encoders[format_name]&.encode_event event: event,
|
267
|
+
data_encoder: @data_encoders,
|
268
|
+
**format_args
|
269
|
+
return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result
|
270
|
+
raise ::ArgumentError, "Unknown format name: #{format_name.inspect}"
|
189
271
|
end
|
190
272
|
|
191
273
|
##
|
192
|
-
# Encode a batch of events
|
274
|
+
# Encode a batch of events in structured content mode in the given format.
|
193
275
|
#
|
194
|
-
#
|
195
|
-
# list (as defined in the Rack specification) and the second is a string
|
196
|
-
# containing the HTTP body content. The headers list will contain only
|
197
|
-
# one header, a `Content-Type` whose value is of the form
|
198
|
-
# `application/cloudevents-batch+format`.
|
276
|
+
# @deprecated Will be removed in version 1.0. Use {#encode_event} instead.
|
199
277
|
#
|
200
|
-
# @param
|
201
|
-
# @param
|
278
|
+
# @param event_batch [Array<CloudEvents::Event>] The batch of events.
|
279
|
+
# @param format_name [String] The format name.
|
202
280
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
203
281
|
# @return [Array(headers,String)]
|
204
282
|
#
|
205
|
-
def encode_batched_content
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
|
283
|
+
def encode_batched_content event_batch, format_name, **format_args
|
284
|
+
result = @event_encoders[format_name]&.encode_event event_batch: event_batch,
|
285
|
+
data_encoder: @data_encoders,
|
286
|
+
**format_args
|
287
|
+
return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result
|
288
|
+
raise ::ArgumentError, "Unknown format name: #{format_name.inspect}"
|
212
289
|
end
|
213
290
|
|
214
291
|
##
|
215
|
-
# Encode an event
|
292
|
+
# Encode an event in binary content mode.
|
216
293
|
#
|
217
|
-
#
|
218
|
-
# list (as defined in the Rack specification) and the second is a string
|
219
|
-
# containing the HTTP body content.
|
294
|
+
# @deprecated Will be removed in version 1.0. Use {#encode_event} instead.
|
220
295
|
#
|
221
296
|
# @param event [CloudEvents::Event] The event.
|
297
|
+
# @param format_args [keywords] Extra args to pass to the formatter.
|
222
298
|
# @return [Array(headers,String)]
|
223
299
|
#
|
224
|
-
def encode_binary_content event
|
300
|
+
def encode_binary_content event, legacy_data_encode: true, **format_args
|
225
301
|
headers = {}
|
226
|
-
body = nil
|
227
302
|
event.to_h.each do |key, value|
|
228
|
-
|
229
|
-
when "data"
|
230
|
-
body = value
|
231
|
-
when "datacontenttype"
|
232
|
-
headers["Content-Type"] = value
|
233
|
-
else
|
303
|
+
unless ["data", "data_encoded", "datacontenttype"].include? key
|
234
304
|
headers["CE-#{key}"] = percent_encode value
|
235
305
|
end
|
236
306
|
end
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
headers["Content-Type"] ||= "application/json; charset=#{body.encoding.name.downcase}"
|
245
|
-
end
|
307
|
+
body, content_type =
|
308
|
+
if legacy_data_encode || event.spec_version.start_with?("0.")
|
309
|
+
legacy_extract_event_data event
|
310
|
+
else
|
311
|
+
normal_extract_event_data event, format_args
|
312
|
+
end
|
313
|
+
headers["Content-Type"] = content_type.to_s if content_type
|
246
314
|
[headers, body]
|
247
315
|
end
|
248
316
|
|
249
317
|
##
|
250
318
|
# Decode a percent-encoded string to a UTF-8 string.
|
251
319
|
#
|
320
|
+
# @private
|
321
|
+
#
|
252
322
|
# @param str [String] Incoming ascii string from an HTTP header, with one
|
253
323
|
# cycle of percent-encoding.
|
254
324
|
# @return [String] Resulting decoded string in UTF-8.
|
255
325
|
#
|
256
326
|
def percent_decode str
|
327
|
+
str = str.gsub(/"((?:[^"\\]|\\.)*)"/) { ::Regexp.last_match(1).gsub(/\\(.)/, '\1') }
|
257
328
|
decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1..-1].to_i(16)].pack "C" }
|
258
329
|
decoded_str.force_encoding ::Encoding::UTF_8
|
259
330
|
end
|
@@ -263,6 +334,8 @@ module CloudEvents
|
|
263
334
|
# non-printing and non-ascii characters to result in an ASCII string
|
264
335
|
# suitable for setting as an HTTP header value.
|
265
336
|
#
|
337
|
+
# @private
|
338
|
+
#
|
266
339
|
# @param str [String] Incoming arbitrary string that can be represented
|
267
340
|
# in UTF-8.
|
268
341
|
# @return [String] Resulting encoded string in ASCII.
|
@@ -271,7 +344,7 @@ module CloudEvents
|
|
271
344
|
arr = []
|
272
345
|
utf_str = str.to_s.encode ::Encoding::UTF_8
|
273
346
|
utf_str.each_byte do |byte|
|
274
|
-
if byte >= 33 && byte <= 126 && byte != 37
|
347
|
+
if byte >= 33 && byte <= 126 && byte != 34 && byte != 37
|
275
348
|
arr << byte
|
276
349
|
else
|
277
350
|
hi = byte / 16
|
@@ -286,23 +359,135 @@ module CloudEvents
|
|
286
359
|
|
287
360
|
private
|
288
361
|
|
362
|
+
def add_named_formatter collection, formatter, name
|
363
|
+
return unless name
|
364
|
+
formatters = collection[name] ||= []
|
365
|
+
formatters.unshift formatter unless formatters.include? formatter
|
366
|
+
end
|
367
|
+
|
368
|
+
##
|
369
|
+
# Decode a single event from the given request body and content type in
|
370
|
+
# structured mode.
|
371
|
+
#
|
372
|
+
def decode_structured_content content, content_type, allow_opaque, **format_args
|
373
|
+
result = @event_decoders.decode_event content: content,
|
374
|
+
content_type: content_type,
|
375
|
+
data_decoder: @data_decoders,
|
376
|
+
**format_args
|
377
|
+
return result[:event] || result[:event_batch] if result
|
378
|
+
if content_type&.media_type == "application" &&
|
379
|
+
["cloudevents", "cloudevents-batch"].include?(content_type.subtype_base)
|
380
|
+
return Event::Opaque.new content, content_type if allow_opaque
|
381
|
+
raise UnsupportedFormatError, "Unknown cloudevents content type: #{content_type}"
|
382
|
+
end
|
383
|
+
nil
|
384
|
+
end
|
385
|
+
|
386
|
+
##
|
387
|
+
# Decode an event from the given Rack environment in binary content mode.
|
388
|
+
#
|
389
|
+
# TODO: legacy_data_decode is deprecated and can be removed when
|
390
|
+
# decode_rack_env is removed.
|
391
|
+
#
|
392
|
+
def decode_binary_content content, content_type, env, legacy_data_decode, **format_args
|
393
|
+
spec_version = env["HTTP_CE_SPECVERSION"]
|
394
|
+
return nil unless spec_version
|
395
|
+
unless spec_version =~ /^0\.3|1(\.|$)/
|
396
|
+
raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
|
397
|
+
end
|
398
|
+
attributes = { "spec_version" => spec_version }
|
399
|
+
if legacy_data_decode || spec_version.start_with?("0.")
|
400
|
+
legacy_populate_data_attributes attributes, content, content_type
|
401
|
+
else
|
402
|
+
normal_populate_data_attributes attributes, content, content_type, spec_version, format_args
|
403
|
+
end
|
404
|
+
populate_attributes_from_env attributes, env
|
405
|
+
Event.create spec_version: spec_version, set_attributes: attributes
|
406
|
+
end
|
407
|
+
|
408
|
+
def legacy_populate_data_attributes attributes, content, content_type
|
409
|
+
attributes["data"] = content
|
410
|
+
attributes["data_content_type"] = content_type if content_type
|
411
|
+
end
|
412
|
+
|
413
|
+
def normal_populate_data_attributes attributes, content, content_type, spec_version, format_args
|
414
|
+
attributes["data_encoded"] = content
|
415
|
+
result = @data_decoders.decode_data spec_version: spec_version,
|
416
|
+
content: content,
|
417
|
+
content_type: content_type,
|
418
|
+
**format_args
|
419
|
+
if result
|
420
|
+
attributes["data"] = result[:data]
|
421
|
+
content_type = result[:content_type]
|
422
|
+
end
|
423
|
+
attributes["data_content_type"] = content_type if content_type
|
424
|
+
end
|
425
|
+
|
426
|
+
def populate_attributes_from_env attributes, env
|
427
|
+
omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"]
|
428
|
+
env.each do |key, value|
|
429
|
+
match = /^HTTP_CE_(\w+)$/.match key
|
430
|
+
next unless match
|
431
|
+
attr_name = match[1].downcase
|
432
|
+
attributes[attr_name] = percent_decode value unless omit_names.include? attr_name
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def legacy_extract_event_data event
|
437
|
+
body = event.data
|
438
|
+
content_type = event.data_content_type&.to_s
|
439
|
+
case body
|
440
|
+
when ::String
|
441
|
+
[body, content_type || string_content_type(body)]
|
442
|
+
when nil
|
443
|
+
[nil, nil]
|
444
|
+
else
|
445
|
+
[::JSON.dump(body), content_type || "application/json; charset=#{body.encoding.name.downcase}"]
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def normal_extract_event_data event, format_args
|
450
|
+
body = event.data_encoded
|
451
|
+
if body
|
452
|
+
[body, event.data_content_type]
|
453
|
+
elsif event.data?
|
454
|
+
result = @data_encoders.encode_data spec_version: event.spec_version,
|
455
|
+
data: event.data,
|
456
|
+
content_type: event.data_content_type,
|
457
|
+
**format_args
|
458
|
+
raise UnsupportedFormatError, "Could not encode unknown content-type: #{content_type}" unless result
|
459
|
+
[result[:content], result[:content_type]]
|
460
|
+
else
|
461
|
+
["", nil]
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
289
465
|
def read_with_charset io, charset
|
466
|
+
return nil if io.nil?
|
290
467
|
str = io.read
|
291
468
|
if charset
|
292
469
|
begin
|
293
470
|
str.force_encoding charset
|
294
471
|
rescue ::ArgumentError
|
295
|
-
#
|
472
|
+
# Use binary for now if the charset is unrecognized
|
473
|
+
str.force_encoding ::Encoding::ASCII_8BIT
|
296
474
|
end
|
297
475
|
end
|
298
476
|
str
|
299
477
|
end
|
300
478
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
479
|
+
# @private
|
480
|
+
module DefaultDataFormat
|
481
|
+
# @private
|
482
|
+
def self.decode_data content: nil, content_type: nil, **_extra_kwargs
|
483
|
+
return nil unless content_type.nil?
|
484
|
+
{ data: content, content_type: nil }
|
485
|
+
end
|
486
|
+
|
487
|
+
# @private
|
488
|
+
def self.encode_data data: nil, content_type: nil, **_extra_kwargs
|
489
|
+
return nil unless content_type.nil?
|
490
|
+
{ content: data.to_s, content_type: nil }
|
306
491
|
end
|
307
492
|
end
|
308
493
|
end
|