cloud_events 0.7.0 → 0.8.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/.yardopts +1 -1
- data/CHANGELOG.md +13 -0
- data/CONTRIBUTING.md +167 -0
- data/MAINTAINERS.md +5 -0
- data/README.md +8 -38
- data/RELEASING.md +25 -0
- data/lib/cloud_events/content_type.rb +32 -32
- data/lib/cloud_events/event/field_interpreter.rb +46 -38
- data/lib/cloud_events/event/opaque.rb +3 -3
- data/lib/cloud_events/event/utils.rb +9 -9
- data/lib/cloud_events/event/v0.rb +19 -19
- data/lib/cloud_events/event/v1.rb +21 -21
- data/lib/cloud_events/event.rb +4 -4
- data/lib/cloud_events/format.rb +13 -14
- data/lib/cloud_events/http_binding.rb +88 -88
- data/lib/cloud_events/json_format.rb +65 -66
- data/lib/cloud_events/text_format.rb +6 -7
- data/lib/cloud_events/version.rb +1 -1
- metadata +9 -10
- data/LICENSE.md +0 -202
|
@@ -31,7 +31,7 @@ module CloudEvents
|
|
|
31
31
|
def self.default
|
|
32
32
|
@default ||= begin
|
|
33
33
|
http_binding = new
|
|
34
|
-
http_binding.register_formatter
|
|
34
|
+
http_binding.register_formatter(JsonFormat.new, encoder_name: JSON_FORMAT)
|
|
35
35
|
http_binding.default_encoder_name = JSON_FORMAT
|
|
36
36
|
http_binding
|
|
37
37
|
end
|
|
@@ -46,14 +46,14 @@ module CloudEvents
|
|
|
46
46
|
end
|
|
47
47
|
@event_encoders = {}
|
|
48
48
|
@data_decoders = Format::Multi.new do |result|
|
|
49
|
-
result&.key?(:data) && result
|
|
49
|
+
result&.key?(:data) && result.key?(:content_type) ? result : nil
|
|
50
50
|
end
|
|
51
51
|
@data_encoders = Format::Multi.new do |result|
|
|
52
|
-
result&.key?(:content) && result
|
|
52
|
+
result&.key?(:content) && result.key?(:content_type) ? result : nil
|
|
53
53
|
end
|
|
54
54
|
text_format = TextFormat.new
|
|
55
|
-
@data_decoders.formats.replace
|
|
56
|
-
@data_encoders.formats.replace
|
|
55
|
+
@data_decoders.formats.replace([text_format, DefaultDataFormat])
|
|
56
|
+
@data_encoders.formats.replace([text_format, DefaultDataFormat])
|
|
57
57
|
|
|
58
58
|
@default_encoder_name = nil
|
|
59
59
|
end
|
|
@@ -71,18 +71,18 @@ module CloudEvents
|
|
|
71
71
|
# and will be removed in version 1.0. Use encoder_name instead.
|
|
72
72
|
# @return [self]
|
|
73
73
|
#
|
|
74
|
-
def register_formatter
|
|
74
|
+
def register_formatter(formatter, deprecated_name = nil, encoder_name: nil)
|
|
75
75
|
encoder_name ||= deprecated_name
|
|
76
76
|
encoder_name = encoder_name.to_s.strip.downcase if encoder_name
|
|
77
|
-
decode_event = formatter.respond_to?
|
|
78
|
-
encode_event = encoder_name if formatter.respond_to?
|
|
79
|
-
decode_data = formatter.respond_to?
|
|
80
|
-
encode_data = formatter.respond_to?
|
|
81
|
-
register_formatter_methods
|
|
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
82
|
decode_event: decode_event,
|
|
83
83
|
encode_event: encode_event,
|
|
84
84
|
decode_data: decode_data,
|
|
85
|
-
encode_data: encode_data
|
|
85
|
+
encode_data: encode_data)
|
|
86
86
|
self
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -102,20 +102,20 @@ module CloudEvents
|
|
|
102
102
|
# {CloudEvents::Format#encode_data} method.
|
|
103
103
|
# @return [self]
|
|
104
104
|
#
|
|
105
|
-
def register_formatter_methods
|
|
105
|
+
def register_formatter_methods(formatter,
|
|
106
106
|
decode_event: false,
|
|
107
107
|
encode_event: nil,
|
|
108
108
|
decode_data: false,
|
|
109
|
-
encode_data: false
|
|
110
|
-
@event_decoders.formats.unshift
|
|
109
|
+
encode_data: false)
|
|
110
|
+
@event_decoders.formats.unshift(formatter) if decode_event
|
|
111
111
|
if encode_event
|
|
112
112
|
encoders = @event_encoders[encode_event] ||= Format::Multi.new do |result|
|
|
113
|
-
result&.key?(:content) && result
|
|
113
|
+
result&.key?(:content) && result.key?(:content_type) ? result : nil
|
|
114
114
|
end
|
|
115
|
-
encoders.formats.unshift
|
|
115
|
+
encoders.formats.unshift(formatter)
|
|
116
116
|
end
|
|
117
|
-
@data_decoders.formats.unshift
|
|
118
|
-
@data_encoders.formats.unshift
|
|
117
|
+
@data_decoders.formats.unshift(formatter) if decode_data
|
|
118
|
+
@data_encoders.formats.unshift(formatter) if encode_data
|
|
119
119
|
self
|
|
120
120
|
end
|
|
121
121
|
|
|
@@ -136,10 +136,10 @@ module CloudEvents
|
|
|
136
136
|
# @param env [Hash] The Rack environment.
|
|
137
137
|
# @return [boolean] Whether the request is likely a CloudEvent.
|
|
138
138
|
#
|
|
139
|
-
def probable_event?
|
|
140
|
-
return false if ILLEGAL_METHODS.include?
|
|
139
|
+
def probable_event?(env)
|
|
140
|
+
return false if ILLEGAL_METHODS.include?(env["REQUEST_METHOD"])
|
|
141
141
|
return true if env["HTTP_CE_SPECVERSION"]
|
|
142
|
-
content_type = ContentType.new
|
|
142
|
+
content_type = ContentType.new(env["CONTENT_TYPE"].to_s)
|
|
143
143
|
content_type.media_type == "application" &&
|
|
144
144
|
["cloudevents", "cloudevents-batch"].include?(content_type.subtype_base)
|
|
145
145
|
end
|
|
@@ -166,17 +166,17 @@ module CloudEvents
|
|
|
166
166
|
# @raise [CloudEvents::CloudEventsError] if an event could not be decoded
|
|
167
167
|
# from the request.
|
|
168
168
|
#
|
|
169
|
-
def decode_event
|
|
169
|
+
def decode_event(env, allow_opaque: false, **format_args)
|
|
170
170
|
request_method = env["REQUEST_METHOD"]
|
|
171
|
-
raise
|
|
171
|
+
raise(NotCloudEventError, "Request method is #{request_method}") if ILLEGAL_METHODS.include?(request_method)
|
|
172
172
|
content_type_string = env["CONTENT_TYPE"]
|
|
173
|
-
content_type = ContentType.new
|
|
174
|
-
content = read_with_charset
|
|
173
|
+
content_type = ContentType.new(content_type_string) if content_type_string
|
|
174
|
+
content = read_with_charset(env["rack.input"], content_type&.charset)
|
|
175
175
|
result = decode_binary_content(content, content_type, env, false, **format_args) ||
|
|
176
176
|
decode_structured_content(content, content_type, allow_opaque, **format_args)
|
|
177
177
|
if result.nil?
|
|
178
178
|
content_type_string = content_type_string ? content_type_string.inspect : "not present"
|
|
179
|
-
raise
|
|
179
|
+
raise(NotCloudEventError, "Content-Type is #{content_type_string}, and CE-SpecVersion is not present")
|
|
180
180
|
end
|
|
181
181
|
result
|
|
182
182
|
end
|
|
@@ -204,24 +204,24 @@ module CloudEvents
|
|
|
204
204
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
|
205
205
|
# @return [Array(headers,String)]
|
|
206
206
|
#
|
|
207
|
-
def encode_event
|
|
208
|
-
if event.is_a?
|
|
207
|
+
def encode_event(event, structured_format: false, **format_args)
|
|
208
|
+
if event.is_a?(Event::Opaque)
|
|
209
209
|
[{ "Content-Type" => event.content_type.to_s }, event.content]
|
|
210
210
|
elsif !structured_format
|
|
211
|
-
if event.is_a?
|
|
212
|
-
raise
|
|
211
|
+
if event.is_a?(::Array)
|
|
212
|
+
raise(::ArgumentError, "Encoding a batch requires structured_format")
|
|
213
213
|
end
|
|
214
|
-
encode_binary_content
|
|
214
|
+
encode_binary_content(event, legacy_data_encode: false, **format_args)
|
|
215
215
|
else
|
|
216
216
|
structured_format = default_encoder_name if structured_format == true
|
|
217
|
-
raise
|
|
217
|
+
raise(::ArgumentError, "Format name not specified, and no default is set") unless structured_format
|
|
218
218
|
case event
|
|
219
219
|
when ::Array
|
|
220
|
-
encode_batched_content
|
|
220
|
+
encode_batched_content(event, structured_format, **format_args)
|
|
221
221
|
when Event
|
|
222
|
-
encode_structured_content
|
|
222
|
+
encode_structured_content(event, structured_format, **format_args)
|
|
223
223
|
else
|
|
224
|
-
raise
|
|
224
|
+
raise(::ArgumentError, "Unknown event type: #{event.class}")
|
|
225
225
|
end
|
|
226
226
|
end
|
|
227
227
|
end
|
|
@@ -243,10 +243,10 @@ module CloudEvents
|
|
|
243
243
|
# @raise [CloudEvents::CloudEventsError] if the request appears to be a
|
|
244
244
|
# CloudEvent but decoding failed.
|
|
245
245
|
#
|
|
246
|
-
def decode_rack_env
|
|
246
|
+
def decode_rack_env(env, **format_args)
|
|
247
247
|
content_type_string = env["CONTENT_TYPE"]
|
|
248
|
-
content_type = ContentType.new
|
|
249
|
-
content = read_with_charset
|
|
248
|
+
content_type = ContentType.new(content_type_string) if content_type_string
|
|
249
|
+
content = read_with_charset(env["rack.input"], content_type&.charset)
|
|
250
250
|
env["rack.input"].rewind rescue nil
|
|
251
251
|
decode_binary_content(content, content_type, env, true, **format_args) ||
|
|
252
252
|
decode_structured_content(content, content_type, false, **format_args)
|
|
@@ -262,12 +262,12 @@ module CloudEvents
|
|
|
262
262
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
|
263
263
|
# @return [Array(headers,String)]
|
|
264
264
|
#
|
|
265
|
-
def encode_structured_content
|
|
266
|
-
result = @event_encoders[format_name]&.encode_event
|
|
265
|
+
def encode_structured_content(event, format_name, **format_args)
|
|
266
|
+
result = @event_encoders[format_name]&.encode_event(event: event,
|
|
267
267
|
data_encoder: @data_encoders,
|
|
268
|
-
**format_args
|
|
268
|
+
**format_args)
|
|
269
269
|
return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result
|
|
270
|
-
raise
|
|
270
|
+
raise(::ArgumentError, "Unknown format name: #{format_name.inspect}")
|
|
271
271
|
end
|
|
272
272
|
|
|
273
273
|
##
|
|
@@ -280,12 +280,12 @@ module CloudEvents
|
|
|
280
280
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
|
281
281
|
# @return [Array(headers,String)]
|
|
282
282
|
#
|
|
283
|
-
def encode_batched_content
|
|
284
|
-
result = @event_encoders[format_name]&.encode_event
|
|
283
|
+
def encode_batched_content(event_batch, format_name, **format_args)
|
|
284
|
+
result = @event_encoders[format_name]&.encode_event(event_batch: event_batch,
|
|
285
285
|
data_encoder: @data_encoders,
|
|
286
|
-
**format_args
|
|
286
|
+
**format_args)
|
|
287
287
|
return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result
|
|
288
|
-
raise
|
|
288
|
+
raise(::ArgumentError, "Unknown format name: #{format_name.inspect}")
|
|
289
289
|
end
|
|
290
290
|
|
|
291
291
|
##
|
|
@@ -297,18 +297,18 @@ module CloudEvents
|
|
|
297
297
|
# @param format_args [keywords] Extra args to pass to the formatter.
|
|
298
298
|
# @return [Array(headers,String)]
|
|
299
299
|
#
|
|
300
|
-
def encode_binary_content
|
|
300
|
+
def encode_binary_content(event, legacy_data_encode: true, **format_args)
|
|
301
301
|
headers = {}
|
|
302
302
|
event.to_h.each do |key, value|
|
|
303
|
-
unless ["data", "data_encoded", "datacontenttype"].include?
|
|
304
|
-
headers["CE-#{key}"] = percent_encode
|
|
303
|
+
unless ["data", "data_encoded", "datacontenttype"].include?(key)
|
|
304
|
+
headers["CE-#{key}"] = percent_encode(value)
|
|
305
305
|
end
|
|
306
306
|
end
|
|
307
307
|
body, content_type =
|
|
308
308
|
if legacy_data_encode || event.spec_version.start_with?("0.")
|
|
309
|
-
legacy_extract_event_data
|
|
309
|
+
legacy_extract_event_data(event)
|
|
310
310
|
else
|
|
311
|
-
normal_extract_event_data
|
|
311
|
+
normal_extract_event_data(event, format_args)
|
|
312
312
|
end
|
|
313
313
|
headers["Content-Type"] = content_type.to_s if content_type
|
|
314
314
|
[headers, body]
|
|
@@ -323,10 +323,10 @@ module CloudEvents
|
|
|
323
323
|
# cycle of percent-encoding.
|
|
324
324
|
# @return [String] Resulting decoded string in UTF-8.
|
|
325
325
|
#
|
|
326
|
-
def percent_decode
|
|
326
|
+
def percent_decode(str)
|
|
327
327
|
str = str.gsub(/"((?:[^"\\]|\\.)*)"/) { ::Regexp.last_match(1).gsub(/\\(.)/, '\1') }
|
|
328
|
-
decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1
|
|
329
|
-
decoded_str.force_encoding
|
|
328
|
+
decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1..].to_i(16)].pack("C") }
|
|
329
|
+
decoded_str.force_encoding(::Encoding::UTF_8)
|
|
330
330
|
end
|
|
331
331
|
|
|
332
332
|
##
|
|
@@ -340,9 +340,9 @@ module CloudEvents
|
|
|
340
340
|
# in UTF-8.
|
|
341
341
|
# @return [String] Resulting encoded string in ASCII.
|
|
342
342
|
#
|
|
343
|
-
def percent_encode
|
|
343
|
+
def percent_encode(str)
|
|
344
344
|
arr = []
|
|
345
|
-
utf_str = str.to_s.encode
|
|
345
|
+
utf_str = str.to_s.encode(::Encoding::UTF_8)
|
|
346
346
|
utf_str.each_byte do |byte|
|
|
347
347
|
if byte >= 33 && byte <= 126 && byte != 34 && byte != 37
|
|
348
348
|
arr << byte
|
|
@@ -354,31 +354,31 @@ module CloudEvents
|
|
|
354
354
|
arr << 37 << hi << lo
|
|
355
355
|
end
|
|
356
356
|
end
|
|
357
|
-
arr.pack
|
|
357
|
+
arr.pack("C*")
|
|
358
358
|
end
|
|
359
359
|
|
|
360
360
|
private
|
|
361
361
|
|
|
362
|
-
def add_named_formatter
|
|
362
|
+
def add_named_formatter(collection, formatter, name)
|
|
363
363
|
return unless name
|
|
364
364
|
formatters = collection[name] ||= []
|
|
365
|
-
formatters.unshift
|
|
365
|
+
formatters.unshift(formatter) unless formatters.include?(formatter)
|
|
366
366
|
end
|
|
367
367
|
|
|
368
368
|
##
|
|
369
369
|
# Decode a single event from the given request body and content type in
|
|
370
370
|
# structured mode.
|
|
371
371
|
#
|
|
372
|
-
def decode_structured_content
|
|
373
|
-
result = @event_decoders.decode_event
|
|
372
|
+
def decode_structured_content(content, content_type, allow_opaque, **format_args)
|
|
373
|
+
result = @event_decoders.decode_event(content: content,
|
|
374
374
|
content_type: content_type,
|
|
375
375
|
data_decoder: @data_decoders,
|
|
376
|
-
**format_args
|
|
376
|
+
**format_args)
|
|
377
377
|
return result[:event] || result[:event_batch] if result
|
|
378
378
|
if content_type&.media_type == "application" &&
|
|
379
379
|
["cloudevents", "cloudevents-batch"].include?(content_type.subtype_base)
|
|
380
|
-
return Event::Opaque.new
|
|
381
|
-
raise
|
|
380
|
+
return Event::Opaque.new(content, content_type) if allow_opaque
|
|
381
|
+
raise(UnsupportedFormatError, "Unknown cloudevents content type: #{content_type}")
|
|
382
382
|
end
|
|
383
383
|
nil
|
|
384
384
|
end
|
|
@@ -389,33 +389,33 @@ module CloudEvents
|
|
|
389
389
|
# TODO: legacy_data_decode is deprecated and can be removed when
|
|
390
390
|
# decode_rack_env is removed.
|
|
391
391
|
#
|
|
392
|
-
def decode_binary_content
|
|
392
|
+
def decode_binary_content(content, content_type, env, legacy_data_decode, **format_args)
|
|
393
393
|
spec_version = env["HTTP_CE_SPECVERSION"]
|
|
394
394
|
return nil unless spec_version
|
|
395
395
|
unless spec_version =~ /^0\.3|1(\.|$)/
|
|
396
|
-
raise
|
|
396
|
+
raise(SpecVersionError, "Unrecognized specversion: #{spec_version}")
|
|
397
397
|
end
|
|
398
398
|
attributes = { "spec_version" => spec_version }
|
|
399
399
|
if legacy_data_decode || spec_version.start_with?("0.")
|
|
400
|
-
legacy_populate_data_attributes
|
|
400
|
+
legacy_populate_data_attributes(attributes, content, content_type)
|
|
401
401
|
else
|
|
402
|
-
normal_populate_data_attributes
|
|
402
|
+
normal_populate_data_attributes(attributes, content, content_type, spec_version, format_args)
|
|
403
403
|
end
|
|
404
|
-
populate_attributes_from_env
|
|
405
|
-
Event.create
|
|
404
|
+
populate_attributes_from_env(attributes, env)
|
|
405
|
+
Event.create(spec_version: spec_version, set_attributes: attributes)
|
|
406
406
|
end
|
|
407
407
|
|
|
408
|
-
def legacy_populate_data_attributes
|
|
408
|
+
def legacy_populate_data_attributes(attributes, content, content_type)
|
|
409
409
|
attributes["data"] = content
|
|
410
410
|
attributes["data_content_type"] = content_type if content_type
|
|
411
411
|
end
|
|
412
412
|
|
|
413
|
-
def normal_populate_data_attributes
|
|
413
|
+
def normal_populate_data_attributes(attributes, content, content_type, spec_version, format_args)
|
|
414
414
|
attributes["data_encoded"] = content
|
|
415
|
-
result = @data_decoders.decode_data
|
|
415
|
+
result = @data_decoders.decode_data(spec_version: spec_version,
|
|
416
416
|
content: content,
|
|
417
417
|
content_type: content_type,
|
|
418
|
-
**format_args
|
|
418
|
+
**format_args)
|
|
419
419
|
if result
|
|
420
420
|
attributes["data"] = result[:data]
|
|
421
421
|
content_type = result[:content_type]
|
|
@@ -423,17 +423,17 @@ module CloudEvents
|
|
|
423
423
|
attributes["data_content_type"] = content_type if content_type
|
|
424
424
|
end
|
|
425
425
|
|
|
426
|
-
def populate_attributes_from_env
|
|
426
|
+
def populate_attributes_from_env(attributes, env)
|
|
427
427
|
omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"]
|
|
428
428
|
env.each do |key, value|
|
|
429
|
-
match = /^HTTP_CE_(\w+)$/.match
|
|
429
|
+
match = /^HTTP_CE_(\w+)$/.match(key)
|
|
430
430
|
next unless match
|
|
431
431
|
attr_name = match[1].downcase
|
|
432
|
-
attributes[attr_name] = percent_decode
|
|
432
|
+
attributes[attr_name] = percent_decode(value) unless omit_names.include?(attr_name)
|
|
433
433
|
end
|
|
434
434
|
end
|
|
435
435
|
|
|
436
|
-
def legacy_extract_event_data
|
|
436
|
+
def legacy_extract_event_data(event)
|
|
437
437
|
body = event.data
|
|
438
438
|
content_type = event.data_content_type&.to_s
|
|
439
439
|
case body
|
|
@@ -446,31 +446,31 @@ module CloudEvents
|
|
|
446
446
|
end
|
|
447
447
|
end
|
|
448
448
|
|
|
449
|
-
def normal_extract_event_data
|
|
449
|
+
def normal_extract_event_data(event, format_args)
|
|
450
450
|
body = event.data_encoded
|
|
451
451
|
if body
|
|
452
452
|
[body, event.data_content_type]
|
|
453
453
|
elsif event.data?
|
|
454
|
-
result = @data_encoders.encode_data
|
|
454
|
+
result = @data_encoders.encode_data(spec_version: event.spec_version,
|
|
455
455
|
data: event.data,
|
|
456
456
|
content_type: event.data_content_type,
|
|
457
|
-
**format_args
|
|
458
|
-
raise
|
|
457
|
+
**format_args)
|
|
458
|
+
raise(UnsupportedFormatError, "Could not encode unknown content-type: #{content_type}") unless result
|
|
459
459
|
[result[:content], result[:content_type]]
|
|
460
460
|
else
|
|
461
461
|
["", nil]
|
|
462
462
|
end
|
|
463
463
|
end
|
|
464
464
|
|
|
465
|
-
def read_with_charset
|
|
465
|
+
def read_with_charset(io, charset)
|
|
466
466
|
return nil if io.nil?
|
|
467
467
|
str = io.read
|
|
468
468
|
if charset
|
|
469
469
|
begin
|
|
470
|
-
str.force_encoding
|
|
470
|
+
str.force_encoding(charset)
|
|
471
471
|
rescue ::ArgumentError
|
|
472
472
|
# Use binary for now if the charset is unrecognized
|
|
473
|
-
str.force_encoding
|
|
473
|
+
str.force_encoding(::Encoding::ASCII_8BIT)
|
|
474
474
|
end
|
|
475
475
|
end
|
|
476
476
|
str
|
|
@@ -479,13 +479,13 @@ module CloudEvents
|
|
|
479
479
|
# @private
|
|
480
480
|
module DefaultDataFormat
|
|
481
481
|
# @private
|
|
482
|
-
def self.decode_data
|
|
482
|
+
def self.decode_data(content: nil, content_type: nil, **_extra_kwargs)
|
|
483
483
|
return nil unless content_type.nil?
|
|
484
484
|
{ data: content, content_type: nil }
|
|
485
485
|
end
|
|
486
486
|
|
|
487
487
|
# @private
|
|
488
|
-
def self.encode_data
|
|
488
|
+
def self.encode_data(data: nil, content_type: nil, **_extra_kwargs)
|
|
489
489
|
return nil unless content_type.nil?
|
|
490
490
|
{ content: data.to_s, content_type: nil }
|
|
491
491
|
end
|