cloud_events 0.5.1 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d44f430fb349c68608ecb791c8e0727c7c6cef018022a4f28c6dcb140b201d48
4
- data.tar.gz: d4f1755b09f71fe1817d227666762c642de19172062a7fbdc29ec2a837a7b31e
3
+ metadata.gz: e30cd516cae86c634b7fd69990ba1297a5e53fa20a7c5630b69f9f356c93589e
4
+ data.tar.gz: d7e6447bf17e5e18ba70153106cd8d6e3f73aa2704d9854b3f5360ccf7f869f6
5
5
  SHA512:
6
- metadata.gz: c0f36a2c0cf43efafbda5b94a7392e5c52ff7974df002d0051013a4db1bd809d62721499c2f0c5f1712907f72dbbea6e2280532d1ac275a0aa33e96e4094eb19
7
- data.tar.gz: c18abd09d34d4e7f51ac2f327d10ae99ab7541ee2f13afa2df46c67424e40bc2e99521f64341436858c686261cdab315945173940cd467fa55bc49ead4590096
6
+ metadata.gz: 2d61d33ba16d60b49b98a85d5b0253751598dc166f197f41af822ded7a7a0c742ddeae4981040267ad59ee7364fd9c967f5ec92fd40d8cfd43c81528c3994de9
7
+ data.tar.gz: 9de42c4f1147338b8693d4bae17f1464eaa4cc4c50b8b0e3b3613c7a8659eea7a3fe17ee5583a874e42b0d17c4eead5ebb92eb2b9efd246bacb223b7c6f3deab
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.6.0 / 2021-08-23
4
+
5
+ This update further clarifies and cleans up the encoding behavior of event payloads. In particular, the event object now includes explicitly encoded data in the new `data_encoded` field, and provides information on whether the existing `data` field contains an encoded or decoded form of the payload.
6
+
7
+ * Added `data_encoded`, `data_decoded?` and `data?` methods to `CloudEvents::Event::V1`, added `:data_encoded` as an input attribute, and clarified the encoding semantics of each field.
8
+ * Changed `:attributes` keyword argument in event constructors to `:set_attributes`, to avoid any possible collision with a real extension attribute name. (The old argument name is deprecated and will be removed in 1.0.)
9
+ * Fixed various inconsistencies in the data encoding behavior of `JsonFormat` and `HttpBinding`.
10
+ * Support passing a data content encoder/decoder into `JsonFormat#encode_event` and `JsonFormat#decode_event`.
11
+ * Provided `TextFormat` to handle media types with trivial encoding.
12
+ * Provided `Format::Multi` to handle checking a series of encoders/decoders.
13
+
3
14
  ### v0.5.1 / 2021-06-28
4
15
 
5
16
  * ADDED: Add HttpBinding#probable_event?
data/README.md CHANGED
@@ -35,7 +35,7 @@ A simple [Sinatra](https://sinatrarb.com) app that receives CloudEvents:
35
35
  ```ruby
36
36
  # examples/server/Gemfile
37
37
  source "https://rubygems.org"
38
- gem "cloud_events", "~> 0.5"
38
+ gem "cloud_events", "~> 0.6"
39
39
  gem "sinatra", "~> 2.0"
40
40
  ```
41
41
 
@@ -47,7 +47,7 @@ require "cloud_events"
47
47
  cloud_events_http = CloudEvents::HttpBinding.default
48
48
 
49
49
  post "/" do
50
- event = cloud_events_http.decode_rack_env request.env
50
+ event = cloud_events_http.decode_event request.env
51
51
  logger.info "Received CloudEvent: #{event.to_h}"
52
52
  end
53
53
  ```
@@ -59,7 +59,7 @@ A simple Ruby script that sends a CloudEvent:
59
59
  ```ruby
60
60
  # examples/client/Gemfile
61
61
  source "https://rubygems.org"
62
- gem "cloud_events", "~> 0.5"
62
+ gem "cloud_events", "~> 0.6"
63
63
  ```
64
64
 
65
65
  ```ruby
data/lib/cloud_events.rb CHANGED
@@ -6,6 +6,7 @@ require "cloud_events/event"
6
6
  require "cloud_events/format"
7
7
  require "cloud_events/http_binding"
8
8
  require "cloud_events/json_format"
9
+ require "cloud_events/text_format"
9
10
 
10
11
  ##
11
12
  # CloudEvents implementation.
@@ -22,11 +22,11 @@ module CloudEvents
22
22
  @attributes.freeze
23
23
  end
24
24
 
25
- def string keys, required: false
25
+ def string keys, required: false, allow_empty: false
26
26
  object keys, required: required do |value|
27
27
  case value
28
28
  when ::String
29
- raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
29
+ raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty? && !allow_empty
30
30
  value.freeze
31
31
  [value, value]
32
32
  else
@@ -125,7 +125,7 @@ module CloudEvents
125
125
  end
126
126
  if value == UNDEFINED
127
127
  raise AttributeError, "The #{keys.first} field is required" if required
128
- return nil
128
+ return allow_nil ? UNDEFINED : nil
129
129
  end
130
130
  converted, raw = yield value
131
131
  @attributes[keys.first.freeze] = raw
@@ -34,7 +34,10 @@ module CloudEvents
34
34
  # Create a new cloud event object with the given data and attributes.
35
35
  #
36
36
  # Event attributes may be presented as keyword arguments, or as a Hash
37
- # passed in via the `attributes` argument (but not both).
37
+ # passed in via the special `:set_attributes` keyword argument (but not
38
+ # both). The `:set_attributes` keyword argument is useful for passing in
39
+ # attributes whose keys are strings rather than symbols, which some
40
+ # versions of Ruby will not accept as keyword arguments.
38
41
  #
39
42
  # The following standard attributes are supported and exposed as
40
43
  # attribute methods on the object.
@@ -68,16 +71,18 @@ module CloudEvents
68
71
  # `:data` field, for example if you pass a structured hash. If this is an
69
72
  # issue, make a deep copy of objects before passing to this constructor.
70
73
  #
71
- # @param attributes [Hash] The data and attributes, as a hash.
74
+ # @param set_attributes [Hash] The data and attributes, as a hash.
75
+ # (Also available using the deprecated keyword `attributes`.)
72
76
  # @param args [keywords] The data and attributes, as keyword arguments.
73
77
  #
74
- def initialize attributes: nil, **args
75
- interpreter = FieldInterpreter.new attributes || args
78
+ def initialize set_attributes: nil, attributes: nil, **args
79
+ interpreter = FieldInterpreter.new set_attributes || attributes || args
76
80
  @spec_version = interpreter.spec_version ["specversion", "spec_version"], accept: /^0\.3$/
77
81
  @id = interpreter.string ["id"], required: true
78
82
  @source = interpreter.uri ["source"], required: true
79
83
  @type = interpreter.string ["type"], required: true
80
84
  @data = interpreter.data_object ["data"]
85
+ @data = nil if @data == FieldInterpreter::UNDEFINED
81
86
  @data_content_encoding = interpreter.string ["datacontentencoding", "data_content_encoding"]
82
87
  @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
83
88
  @schema_url = interpreter.uri ["schemaurl", "schema_url"]
@@ -98,7 +103,7 @@ module CloudEvents
98
103
  #
99
104
  def with **changes
100
105
  attributes = @attributes.merge changes
101
- V0.new attributes: attributes
106
+ V0.new set_attributes: attributes
102
107
  end
103
108
 
104
109
  ##
@@ -14,10 +14,29 @@ module CloudEvents
14
14
  # This object represents a complete CloudEvent, including the event data
15
15
  # and context attributes. It supports the standard required and optional
16
16
  # attributes defined in CloudEvents V1.0, and arbitrary extension
17
- # attributes. All attribute values can be obtained (in their string form)
17
+ # attributes.
18
+ #
19
+ # Values for most attributes can be obtained in their encoded string form
18
20
  # via the {Event::V1#[]} method. Additionally, standard attributes have
19
- # their own accessor methods that may return typed objects (such as
20
- # `DateTime` for the `time` attribute).
21
+ # their own accessor methods that may return decoded Ruby objects (such as
22
+ # a `DateTime` object for the `time` attribute).
23
+ #
24
+ # The `data` attribute is treated specially because it is subject to
25
+ # arbitrary encoding governed by the `datacontenttype` attribute. Data is
26
+ # expressed in two related fields: {Event::V1#data} and
27
+ # {Event::V1#data_encoded}. The former, `data`, _may_ be an arbitrary Ruby
28
+ # object representing the decoded form of the data (for example, a Hash for
29
+ # most JSON-formatted data.) The latter, `data_encoded`, _must_, if
30
+ # present, be a Ruby String object representing the encoded string or
31
+ # byte array form of the data.
32
+ #
33
+ # When the CloudEvents Ruby SDK encodes an event for transmission, it will
34
+ # use the `data_encoded` field if present. Otherwise, it will attempt to
35
+ # encode the `data` field using any available encoder that recognizes the
36
+ # content-type. Currently, text and JSON types are supported. If the type
37
+ # is not supported, event encoding may fail. It is thus recommended that
38
+ # applications provide a `data_encoded` string, if the `data` object is
39
+ # nontrivially encoded.
21
40
  #
22
41
  # This object is immutable, and Ractor-shareable on Ruby 3. The data and
23
42
  # attribute values can be retrieved but not modified. To obtain an event
@@ -33,8 +52,13 @@ module CloudEvents
33
52
  ##
34
53
  # Create a new cloud event object with the given data and attributes.
35
54
  #
55
+ # ### Specifying event attributes
56
+ #
36
57
  # Event attributes may be presented as keyword arguments, or as a Hash
37
- # passed in via the `attributes` argument (but not both).
58
+ # passed in via the special `:set_attributes` keyword argument (but not
59
+ # both). The `:set_attributes` keyword argument is useful for passing in
60
+ # attributes whose keys are strings rather than symbols, which some
61
+ # versions of Ruby will not accept as keyword arguments.
38
62
  #
39
63
  # The following standard attributes are supported and exposed as
40
64
  # attribute methods on the object.
@@ -45,11 +69,15 @@ module CloudEvents
45
69
  # * **:source** [`String`, `URI`] - _required_ - The event `source`
46
70
  # field.
47
71
  # * **:type** [`String`] - _required_ - The event `type` field.
48
- # * **:data** [`Object`] - _optional_ - The data associated with the
49
- # event (i.e. the `data` field).
72
+ # * **:data** [`Object`] - _optional_ - The "decoded" Ruby object form
73
+ # of the event `data` field, if known. (e.g. a Hash representing a
74
+ # JSON document)
75
+ # * **:data_encoded** [`String`] - _optional_ - The "encoded" string
76
+ # form of the event `data` field, if known. This should be set along
77
+ # with the `data_content_type`.
50
78
  # * **:data_content_type** (or **:datacontenttype**) [`String`,
51
- # {ContentType}] - _optional_ - The content-type for the data, if
52
- # the data is a string (i.e. the event `datacontenttype` field.)
79
+ # {ContentType}] - _optional_ - The content-type for the encoded data
80
+ # (i.e. the event `datacontenttype` field.)
53
81
  # * **:data_schema** (or **:dataschema**) [`String`, `URI`] -
54
82
  # _optional_ - The event `dataschema` field.
55
83
  # * **:subject** [`String`] - _optional_ - The event `subject` field.
@@ -65,16 +93,63 @@ module CloudEvents
65
93
  # `:data` field, for example if you pass a structured hash. If this is an
66
94
  # issue, make a deep copy of objects before passing to this constructor.
67
95
  #
68
- # @param attributes [Hash] The data and attributes, as a hash.
96
+ # ### Specifying payload data
97
+ #
98
+ # Typically you should provide _both_ the `:data` and `:data_encoded`
99
+ # fields, the former representing the decoded (Ruby object) form of the
100
+ # data, and the second providing a hint to formatters and protocol
101
+ # bindings for how to seralize the data. In this case, the {#data} and
102
+ # {#data_encoded} methods will return the corresponding values, and
103
+ # {#data_decoded?} will return true to indicate that {#data} represents
104
+ # the decoded form.
105
+ #
106
+ # If you provide _only_ the `:data` field, omitting `:data_encoded`, then
107
+ # the value is expected to represent the decoded (Ruby object) form of
108
+ # the data. The {#data} method will return this decoded value, and
109
+ # {#data_decoded?} will return true. The {#data_encoded} method will
110
+ # return nil.
111
+ # When serializing such an event, it will be up to the formatter or
112
+ # protocol binding to encode the data. This means serialization _could_
113
+ # fail if the formatter does not understand the data's content type.
114
+ # Omitting `:data_encoded` is common if the content type is JSON related
115
+ # (e.g. `application/json`) and the event is being encoded in JSON
116
+ # structured format, because the data encoding is trivial. This form can
117
+ # also be used when the content type is `text/*`, for which encoding is
118
+ # also trivial.
119
+ #
120
+ # If you provide _only_ the `:data_encoded` field, omitting `:data`, then
121
+ # the value is expected to represent the encoded (string) form of the
122
+ # data. The {#data_encoded} method will return this value. Additionally,
123
+ # the {#data} method will return the same _encoded_ value, and
124
+ # {#data_decoded?} will return false.
125
+ # Event objects of this form may be returned from a protocol binding when
126
+ # it decodes an event with a `datacontenttype` that it does not know how
127
+ # to interpret. Applications should query {#data_decoded?} to determine
128
+ # whether the {#data} method returns encoded or decoded data.
129
+ #
130
+ # If you provide _neither_ `:data` nor `:data_encoded`, the event will
131
+ # have no payload data. Both {#data} and {#data_encoded} will return nil,
132
+ # and {#data_decoded?} will return false. (Additionally, {#data?} will
133
+ # return false to signal the absence of any data.)
134
+ #
135
+ # @param set_attributes [Hash] The data and attributes, as a hash.
136
+ # (Also available using the deprecated keyword `attributes`.)
69
137
  # @param args [keywords] The data and attributes, as keyword arguments.
70
138
  #
71
- def initialize attributes: nil, **args
72
- interpreter = FieldInterpreter.new attributes || args
139
+ def initialize set_attributes: nil, attributes: nil, **args
140
+ interpreter = FieldInterpreter.new set_attributes || attributes || args
73
141
  @spec_version = interpreter.spec_version ["specversion", "spec_version"], accept: /^1(\.|$)/
74
142
  @id = interpreter.string ["id"], required: true
75
143
  @source = interpreter.uri ["source"], required: true
76
144
  @type = interpreter.string ["type"], required: true
145
+ @data_encoded = interpreter.string ["data_encoded"], allow_empty: true
77
146
  @data = interpreter.data_object ["data"]
147
+ if @data == FieldInterpreter::UNDEFINED
148
+ @data = @data_encoded
149
+ @data_decoded = false
150
+ else
151
+ @data_decoded = true
152
+ end
78
153
  @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
79
154
  @data_schema = interpreter.uri ["dataschema", "data_schema"]
80
155
  @subject = interpreter.string ["subject"]
@@ -93,8 +168,14 @@ module CloudEvents
93
168
  # @return [FunctionFramework::CloudEvents::Event]
94
169
  #
95
170
  def with **changes
96
- attributes = @attributes.merge changes
97
- V1.new attributes: attributes
171
+ changes = Utils.keys_to_strings changes
172
+ attributes = @attributes.dup
173
+ if changes.key?("data") || changes.key?("data_encoded")
174
+ attributes.delete "data"
175
+ attributes.delete "data_encoded"
176
+ end
177
+ attributes.merge! changes
178
+ V1.new set_attributes: attributes
98
179
  end
99
180
 
100
181
  ##
@@ -160,19 +241,61 @@ module CloudEvents
160
241
  alias specversion spec_version
161
242
 
162
243
  ##
163
- # The event-specific data, or `nil` if there is no data.
244
+ # The event `data` field, or `nil` if there is no data.
245
+ #
246
+ # This may return the data as an encoded string _or_ as a decoded Ruby
247
+ # object. The {#data_decoded?} method specifies whether the `data` value
248
+ # is decoded or encoded.
249
+ #
250
+ # In most cases, {#data} returns a decoded value, unless the event was
251
+ # received from a source that could not decode the content. For example,
252
+ # most protocol bindings understand how to decode JSON, so an event
253
+ # received with a {#data_content_type} of `application/json` will usually
254
+ # return a decoded object (usually a Hash) from {#data}.
164
255
  #
165
- # Data may be one of the following types:
166
- # * Binary data, represented by a `String` using the `ASCII-8BIT`
167
- # encoding.
168
- # * A string in some other encoding such as `UTF-8` or `US-ASCII`.
169
- # * Any JSON data type, such as a Boolean, Integer, Array, Hash, or
170
- # `nil`.
256
+ # See also {#data_encoded} and {#data_decoded?}.
171
257
  #
172
- # @return [Object]
258
+ # @return [Object] if containing decoded data
259
+ # @return [String] if containing encoded data
260
+ # @return [nil] if there is no data
173
261
  #
174
262
  attr_reader :data
175
263
 
264
+ ##
265
+ # The encoded string representation of the data, i.e. its raw form used
266
+ # when encoding an event for transmission. This may be `nil` if there is
267
+ # no data, or if the encoded form is not known.
268
+ #
269
+ # See also {#data}.
270
+ #
271
+ # @return [String,nil]
272
+ #
273
+ attr_reader :data_encoded
274
+
275
+ ##
276
+ # Indicates whether the {#data} field returns decoded data.
277
+ #
278
+ # @return [true] if {#data} returns a decoded Ruby object
279
+ # @return [false] if {#data} returns an encoded string or if the event
280
+ # has no data.
281
+ #
282
+ def data_decoded?
283
+ @data_decoded
284
+ end
285
+
286
+ ##
287
+ # Indicates whether the data field is present. If there is no data,
288
+ # {#data} will return `nil`, and {#data_decoded?} will return false.
289
+ #
290
+ # Generally, if there is no data, the {#data_content_type} field should
291
+ # also be absent, but this is not enforced.
292
+ #
293
+ # @return [boolean]
294
+ #
295
+ def data?
296
+ !@data.nil? || @data_decoded
297
+ end
298
+
176
299
  ##
177
300
  # The optional `datacontenttype` field as a {CloudEvents::ContentType}
178
301
  # object, or `nil` if the field is absent.
@@ -8,7 +8,7 @@ module CloudEvents
8
8
  # This module documents the method signatures that may be implemented by
9
9
  # formatters.
10
10
  #
11
- # A formatter is an object that implenets "structured" event encoding and
11
+ # A formatter is an object that implements "structured" event encoding and
12
12
  # decoding strategies for a particular format (such as JSON). In general,
13
13
  # this includes four operations:
14
14
  #
@@ -182,7 +182,7 @@ module CloudEvents
182
182
  # * `:content` (String) The serialized form of the data. This might, for
183
183
  # example, be written to an HTTP request body. Care should be taken to
184
184
  # set the string's encoding properly. In particular, to output binary
185
- # data, the encoding should probably be set to `ASCII_8BIT`.
185
+ # data, the encoding should generally be set to `ASCII_8BIT`.
186
186
  # * `:content_type` ({CloudEvents::ContentType}) The content type for the
187
187
  # output. This might, for example, be written to the `Content-Type`
188
188
  # header of an HTTP request.
@@ -198,5 +198,80 @@ module CloudEvents
198
198
  def encode_data **_kwargs
199
199
  nil
200
200
  end
201
+
202
+ ##
203
+ # A convenience formatter that checks multiple formats for one capable of
204
+ # handling the given input.
205
+ #
206
+ class Multi
207
+ ##
208
+ # Create a multi format.
209
+ #
210
+ # @param formats [Array<Format>] An array of formats to check in order
211
+ # @param result_checker [Proc] An optional block that determines whether
212
+ # a result provided by a format is acceptable. The block takes the
213
+ # format's result as an argument, and returns either the result to
214
+ # indicate acceptability, or `nil` to indicate not.
215
+ #
216
+ def initialize formats = [], &result_checker
217
+ @formats = formats
218
+ @result_checker = result_checker
219
+ end
220
+
221
+ ##
222
+ # The formats to check, in order.
223
+ #
224
+ # @return [Array<Format>]
225
+ #
226
+ attr_reader :formats
227
+
228
+ ##
229
+ # Implements {Format#decode_event}
230
+ #
231
+ def decode_event **kwargs
232
+ @formats.each do |elem|
233
+ result = elem.decode_event(**kwargs)
234
+ result = @result_checker.call result if @result_checker
235
+ return result if result
236
+ end
237
+ nil
238
+ end
239
+
240
+ ##
241
+ # Implements {Format#encode_event}
242
+ #
243
+ def encode_event **kwargs
244
+ @formats.each do |elem|
245
+ result = elem.encode_event(**kwargs)
246
+ result = @result_checker.call result if @result_checker
247
+ return result if result
248
+ end
249
+ nil
250
+ end
251
+
252
+ ##
253
+ # Implements {Format#decode_data}
254
+ #
255
+ def decode_data **kwargs
256
+ @formats.each do |elem|
257
+ result = elem.decode_data(**kwargs)
258
+ result = @result_checker.call result if @result_checker
259
+ return result if result
260
+ end
261
+ nil
262
+ end
263
+
264
+ ##
265
+ # Implements {Format#encode_data}
266
+ #
267
+ def encode_data **kwargs
268
+ @formats.each do |elem|
269
+ result = elem.encode_data(**kwargs)
270
+ result = @result_checker.call result if @result_checker
271
+ return result if result
272
+ end
273
+ nil
274
+ end
275
+ end
201
276
  end
202
277
  end
@@ -28,7 +28,7 @@ module CloudEvents
28
28
  def self.default
29
29
  @default ||= begin
30
30
  http_binding = new
31
- http_binding.register_formatter JsonFormat.new, JSON_FORMAT
31
+ http_binding.register_formatter JsonFormat.new, encoder_name: JSON_FORMAT
32
32
  http_binding.default_encoder_name = JSON_FORMAT
33
33
  http_binding
34
34
  end
@@ -38,10 +38,20 @@ module CloudEvents
38
38
  # Create an empty HTTP binding.
39
39
  #
40
40
  def initialize
41
- @event_decoders = []
41
+ @event_decoders = Format::Multi.new do |result|
42
+ result&.key?(:event) || result&.key?(:event_batch) ? result : nil
43
+ end
42
44
  @event_encoders = {}
43
- @data_decoders = [DefaultDataFormat]
44
- @data_encoders = [DefaultDataFormat]
45
+ @data_decoders = Format::Multi.new do |result|
46
+ result&.key?(:data) && result&.key?(:content_type) ? result : nil
47
+ end
48
+ @data_encoders = Format::Multi.new do |result|
49
+ result&.key?(:content) && result&.key?(:content_type) ? result : nil
50
+ end
51
+ text_format = TextFormat.new
52
+ @data_decoders.formats.replace [text_format, DefaultDataFormat]
53
+ @data_encoders.formats.replace [text_format, DefaultDataFormat]
54
+
45
55
  @default_encoder_name = nil
46
56
  end
47
57
 
@@ -51,15 +61,18 @@ module CloudEvents
51
61
  # {CloudEvents::Format} for a list of possible methods.
52
62
  #
53
63
  # @param formatter [Object] The formatter
54
- # @param name [String] The encoder name under which this formatter will
55
- # register its encode operations. Optional. If not specified, any event
56
- # encoder will _not_ be registered.
64
+ # @param encoder_name [String] The encoder name under which this formatter
65
+ # will register its encode operations. Optional. If not specified, any
66
+ # event encoder will _not_ be registered.
67
+ # @param deprecated_name [String] This positional argument is deprecated
68
+ # and will be removed in version 1.0. Use encoder_name instead.
57
69
  # @return [self]
58
70
  #
59
- def register_formatter formatter, name = nil
60
- name = name.to_s.strip.downcase if name
71
+ def register_formatter formatter, deprecated_name = nil, encoder_name: nil
72
+ encoder_name ||= deprecated_name
73
+ encoder_name = encoder_name.to_s.strip.downcase if encoder_name
61
74
  decode_event = formatter.respond_to? :decode_event
62
- encode_event = name if formatter.respond_to? :encode_event
75
+ encode_event = encoder_name if formatter.respond_to? :encode_event
63
76
  decode_data = formatter.respond_to? :decode_data
64
77
  encode_data = formatter.respond_to? :encode_data
65
78
  register_formatter_methods formatter,
@@ -91,18 +104,21 @@ module CloudEvents
91
104
  encode_event: nil,
92
105
  decode_data: false,
93
106
  encode_data: false
94
- @event_decoders << formatter if decode_event
107
+ @event_decoders.formats.unshift formatter if decode_event
95
108
  if encode_event
96
- formatters = @event_encoders[encode_event] ||= []
97
- formatters << formatter unless formatters.include? formatter
109
+ encoders = @event_encoders[encode_event] ||= Format::Multi.new do |result|
110
+ result&.key?(:content) && result&.key?(:content_type) ? result : nil
111
+ end
112
+ encoders.formats.unshift formatter
98
113
  end
99
- @data_decoders << formatter if decode_data
100
- @data_encoders << formatter if encode_data
114
+ @data_decoders.formats.unshift formatter if decode_data
115
+ @data_encoders.formats.unshift formatter if encode_data
101
116
  self
102
117
  end
103
118
 
104
119
  ##
105
120
  # The name of the encoder to use if none is specified
121
+ #
106
122
  # @return [String,nil]
107
123
  #
108
124
  attr_accessor :default_encoder_name
@@ -150,7 +166,7 @@ module CloudEvents
150
166
  content_type_string = env["CONTENT_TYPE"]
151
167
  content_type = ContentType.new content_type_string if content_type_string
152
168
  content = read_with_charset env["rack.input"], content_type&.charset
153
- result = decode_binary_content(content, content_type, env) ||
169
+ result = decode_binary_content(content, content_type, env, false, **format_args) ||
154
170
  decode_structured_content(content, content_type, allow_opaque, **format_args)
155
171
  if result.nil?
156
172
  content_type_string = content_type_string ? content_type_string.inspect : "not present"
@@ -226,7 +242,7 @@ module CloudEvents
226
242
  content_type = ContentType.new content_type_string if content_type_string
227
243
  content = read_with_charset env["rack.input"], content_type&.charset
228
244
  env["rack.input"].rewind rescue nil
229
- decode_binary_content(content, content_type, env, legacy_data_decode: true) ||
245
+ decode_binary_content(content, content_type, env, true, **format_args) ||
230
246
  decode_structured_content(content, content_type, false, **format_args)
231
247
  end
232
248
 
@@ -241,12 +257,10 @@ module CloudEvents
241
257
  # @return [Array(headers,String)]
242
258
  #
243
259
  def encode_structured_content event, format_name, **format_args
244
- Array(@event_encoders[format_name]).reverse_each do |handler|
245
- result = handler.encode_event event: event, **format_args
246
- if result&.key?(:content) && result&.key?(:content_type)
247
- return [{ "Content-Type" => result[:content_type].to_s }, result[:content]]
248
- end
249
- end
260
+ result = @event_encoders[format_name]&.encode_event event: event,
261
+ data_encoder: @data_encoders,
262
+ **format_args
263
+ return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result
250
264
  raise ::ArgumentError, "Unknown format name: #{format_name.inspect}"
251
265
  end
252
266
 
@@ -261,12 +275,10 @@ module CloudEvents
261
275
  # @return [Array(headers,String)]
262
276
  #
263
277
  def encode_batched_content event_batch, format_name, **format_args
264
- Array(@event_encoders[format_name]).reverse_each do |handler|
265
- result = handler.encode_event event_batch: event_batch, **format_args
266
- if result&.key?(:content) && result&.key?(:content_type)
267
- return [{ "Content-Type" => result[:content_type].to_s }, result[:content]]
268
- end
269
- end
278
+ result = @event_encoders[format_name]&.encode_event event_batch: event_batch,
279
+ data_encoder: @data_encoders,
280
+ **format_args
281
+ return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result
270
282
  raise ::ArgumentError, "Unknown format name: #{format_name.inspect}"
271
283
  end
272
284
 
@@ -282,25 +294,16 @@ module CloudEvents
282
294
  def encode_binary_content event, legacy_data_encode: true, **format_args
283
295
  headers = {}
284
296
  event.to_h.each do |key, value|
285
- unless ["data", "datacontenttype"].include? key
297
+ unless ["data", "data_encoded", "datacontenttype"].include? key
286
298
  headers["CE-#{key}"] = percent_encode value
287
299
  end
288
300
  end
289
- if legacy_data_encode
290
- body = event.data
291
- content_type = event.data_content_type&.to_s
292
- case body
293
- when ::String
294
- content_type ||= string_content_type body
295
- when nil
296
- content_type = nil
301
+ body, content_type =
302
+ if legacy_data_encode || event.spec_version.start_with?("0.")
303
+ legacy_extract_event_data event
297
304
  else
298
- body = ::JSON.dump body
299
- content_type ||= "application/json; charset=#{body.encoding.name.downcase}"
305
+ normal_extract_event_data event, format_args
300
306
  end
301
- else
302
- body, content_type = encode_data event.spec_version, event.data, event.data_content_type, **format_args
303
- end
304
307
  headers["Content-Type"] = content_type.to_s if content_type
305
308
  [headers, body]
306
309
  end
@@ -353,7 +356,7 @@ module CloudEvents
353
356
  def add_named_formatter collection, formatter, name
354
357
  return unless name
355
358
  formatters = collection[name] ||= []
356
- formatters << formatter unless formatters.include? formatter
359
+ formatters.unshift formatter unless formatters.include? formatter
357
360
  end
358
361
 
359
362
  ##
@@ -361,11 +364,11 @@ module CloudEvents
361
364
  # structured mode.
362
365
  #
363
366
  def decode_structured_content content, content_type, allow_opaque, **format_args
364
- @event_decoders.reverse_each do |decoder|
365
- result = decoder.decode_event content: content, content_type: content_type, **format_args
366
- event = result[:event] || result[:event_batch] if result
367
- return event if event
368
- end
367
+ result = @event_decoders.decode_event content: content,
368
+ content_type: content_type,
369
+ data_decoder: @data_decoders,
370
+ **format_args
371
+ return result[:event] || result[:event_batch] if result
369
372
  if content_type&.media_type == "application" &&
370
373
  ["cloudevents", "cloudevents-batch"].include?(content_type.subtype_base)
371
374
  return Event::Opaque.new content, content_type if allow_opaque
@@ -380,19 +383,41 @@ module CloudEvents
380
383
  # TODO: legacy_data_decode is deprecated and can be removed when
381
384
  # decode_rack_env is removed.
382
385
  #
383
- def decode_binary_content content, content_type, env, legacy_data_decode: false
386
+ def decode_binary_content content, content_type, env, legacy_data_decode, **format_args
384
387
  spec_version = env["HTTP_CE_SPECVERSION"]
385
388
  return nil unless spec_version
386
389
  unless spec_version =~ /^0\.3|1(\.|$)/
387
390
  raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
388
391
  end
389
- if legacy_data_decode
390
- data = content
392
+ attributes = { "spec_version" => spec_version }
393
+ if legacy_data_decode || spec_version.start_with?("0.")
394
+ legacy_populate_data_attributes attributes, content, content_type
391
395
  else
392
- data, content_type = decode_data spec_version, content, content_type
396
+ normal_populate_data_attributes attributes, content, content_type, spec_version, format_args
397
+ end
398
+ populate_attributes_from_env attributes, env
399
+ Event.create spec_version: spec_version, set_attributes: attributes
400
+ end
401
+
402
+ def legacy_populate_data_attributes attributes, content, content_type
403
+ attributes["data"] = content
404
+ attributes["data_content_type"] = content_type if content_type
405
+ end
406
+
407
+ def normal_populate_data_attributes attributes, content, content_type, spec_version, format_args
408
+ attributes["data_encoded"] = content
409
+ result = @data_decoders.decode_data spec_version: spec_version,
410
+ content: content,
411
+ content_type: content_type,
412
+ **format_args
413
+ if result
414
+ attributes["data"] = result[:data]
415
+ content_type = result[:content_type]
393
416
  end
394
- attributes = { "spec_version" => spec_version, "data" => data }
395
417
  attributes["data_content_type"] = content_type if content_type
418
+ end
419
+
420
+ def populate_attributes_from_env attributes, env
396
421
  omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"]
397
422
  env.each do |key, value|
398
423
  match = /^HTTP_CE_(\w+)$/.match key
@@ -400,33 +425,35 @@ module CloudEvents
400
425
  attr_name = match[1].downcase
401
426
  attributes[attr_name] = percent_decode value unless omit_names.include? attr_name
402
427
  end
403
- Event.create spec_version: spec_version, attributes: attributes
404
428
  end
405
429
 
406
- def decode_data spec_version, content, content_type, **format_args
407
- @data_decoders.reverse_each do |handler|
408
- result = handler.decode_data spec_version: spec_version,
409
- content: content,
410
- content_type: content_type,
411
- **format_args
412
- if result&.key?(:data) && result&.key?(:content_type)
413
- return [result[:data], result[:content_type]]
414
- end
430
+ def legacy_extract_event_data event
431
+ body = event.data
432
+ content_type = event.data_content_type&.to_s
433
+ case body
434
+ when ::String
435
+ [body, content_type || string_content_type(body)]
436
+ when nil
437
+ [nil, nil]
438
+ else
439
+ [::JSON.dump(body), content_type || "application/json; charset=#{body.encoding.name.downcase}"]
415
440
  end
416
- raise "Should not get here"
417
441
  end
418
442
 
419
- def encode_data spec_version, data_obj, content_type, **format_args
420
- @data_encoders.reverse_each do |handler|
421
- result = handler.encode_data spec_version: spec_version,
422
- data: data_obj,
423
- content_type: content_type,
424
- **format_args
425
- if result&.key?(:content) && result&.key?(:content_type)
426
- return [result[:content], result[:content_type]]
427
- end
443
+ def normal_extract_event_data event, format_args
444
+ body = event.data_encoded
445
+ if body
446
+ [body, event.data_content_type]
447
+ elsif event.data?
448
+ result = @data_encoders.encode_data spec_version: event.spec_version,
449
+ data: event.data,
450
+ content_type: event.data_content_type,
451
+ **format_args
452
+ raise UnsupportedFormatError, "Could not encode unknown content-type: #{content_type}" unless result
453
+ [result[:content], result[:content_type]]
454
+ else
455
+ ["", nil]
428
456
  end
429
- raise "Should not get here"
430
457
  end
431
458
 
432
459
  def read_with_charset io, charset
@@ -447,19 +474,14 @@ module CloudEvents
447
474
  module DefaultDataFormat
448
475
  # @private
449
476
  def self.decode_data content: nil, content_type: nil, **_extra_kwargs
450
- { data: content, content_type: content_type }
477
+ return nil unless content_type.nil?
478
+ { data: content, content_type: nil }
451
479
  end
452
480
 
453
481
  # @private
454
482
  def self.encode_data data: nil, content_type: nil, **_extra_kwargs
455
- content = data.to_s
456
- content_type ||=
457
- if content.encoding == ::Encoding::ASCII_8BIT
458
- "application/octet-stream"
459
- else
460
- "text/plain; charset=#{content.encoding.name.downcase}"
461
- end
462
- { content: content, content_type: content_type }
483
+ return nil unless content_type.nil?
484
+ { content: data.to_s, content_type: nil }
463
485
  end
464
486
  end
465
487
  end
@@ -30,22 +30,24 @@ module CloudEvents
30
30
  #
31
31
  # @param content [String] Serialized content to decode.
32
32
  # @param content_type [CloudEvents::ContentType] The input content type.
33
+ # @param data_decoder [#decode_data] Optional data field decoder, used for
34
+ # non-JSON content types.
33
35
  # @return [Hash] if accepting the request.
34
36
  # @return [nil] if declining the request.
35
37
  # @raise [CloudEvents::FormatSyntaxError] if the JSON could not be parsed
36
38
  # @raise [CloudEvents::SpecVersionError] if an unsupported specversion is
37
39
  # found.
38
40
  #
39
- def decode_event content: nil, content_type: nil, **_other_kwargs
41
+ def decode_event content: nil, content_type: nil, data_decoder: nil, **_other_kwargs
40
42
  return nil unless content && content_type&.media_type == "application" && content_type&.subtype_format == "json"
41
43
  case content_type.subtype_base
42
44
  when "cloudevents"
43
- event = decode_hash_structure ::JSON.parse(content), charset_of(content)
45
+ event = decode_hash_structure ::JSON.parse(content), charset: charset_of(content), data_decoder: data_decoder
44
46
  { event: event }
45
47
  when "cloudevents-batch"
46
48
  charset = charset_of content
47
49
  batch = Array(::JSON.parse(content)).map do |structure|
48
- decode_hash_structure structure, charset
50
+ decode_hash_structure structure, charset: charset, data_decoder: data_decoder
49
51
  end
50
52
  { event_batch: batch }
51
53
  end
@@ -69,18 +71,21 @@ module CloudEvents
69
71
  #
70
72
  # @param event [CloudEvents::Event] An event to encode.
71
73
  # @param event_batch [Array<CloudEvents::Event>] An event batch to encode.
74
+ # @param data_encoder [#encode_data] Optional data field encoder, used for
75
+ # non-JSON content types.
72
76
  # @param sort [boolean] Whether to sort keys of the JSON output.
73
77
  # @return [Hash] if accepting the request.
74
78
  # @return [nil] if declining the request.
79
+ # @raise [CloudEvents::FormatSyntaxError] if the JSON could not be parsed
75
80
  #
76
- def encode_event event: nil, event_batch: nil, sort: false, **_other_kwargs
81
+ def encode_event event: nil, event_batch: nil, data_encoder: nil, sort: false, **_other_kwargs
77
82
  if event && !event_batch
78
- structure = encode_hash_structure event
83
+ structure = encode_hash_structure event, data_encoder: data_encoder
79
84
  structure = sort_keys structure if sort
80
85
  subtype = "cloudevents"
81
86
  elsif event_batch && !event
82
87
  structure = event_batch.map do |elem|
83
- structure_elem = encode_hash_structure elem
88
+ structure_elem = encode_hash_structure elem, data_encoder: data_encoder
84
89
  sort ? sort_keys(structure_elem) : structure_elem
85
90
  end
86
91
  subtype = "cloudevents-batch"
@@ -90,6 +95,8 @@ module CloudEvents
90
95
  content = ::JSON.dump structure
91
96
  content_type = ContentType.new "application/#{subtype}+json; charset=#{charset_of content}"
92
97
  { content: content, content_type: content_type }
98
+ rescue ::JSON::JSONError
99
+ raise FormatSyntaxError, "JSON syntax error"
93
100
  end
94
101
 
95
102
  ##
@@ -116,7 +123,7 @@ module CloudEvents
116
123
  def decode_data spec_version: nil, content: nil, content_type: nil, **_other_kwargs
117
124
  return nil unless spec_version
118
125
  return nil unless content
119
- return nil unless content_type&.subtype_base == "json" || content_type&.subtype_format == "json"
126
+ return nil unless json_content_type? content_type
120
127
  unless spec_version =~ /^0\.3|1(\.|$)/
121
128
  raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
122
129
  end
@@ -151,7 +158,7 @@ module CloudEvents
151
158
  def encode_data spec_version: nil, data: UNSPECIFIED, content_type: nil, sort: false, **_other_kwargs
152
159
  return nil unless spec_version
153
160
  return nil if data == UNSPECIFIED
154
- return nil unless content_type&.subtype_base == "json" || content_type&.subtype_format == "json"
161
+ return nil unless json_content_type? content_type
155
162
  unless spec_version =~ /^0\.3|1(\.|$)/
156
163
  raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
157
164
  end
@@ -164,19 +171,20 @@ module CloudEvents
164
171
  # Decode a single event from a hash data structure with keys and types
165
172
  # conforming to the JSON envelope.
166
173
  #
167
- # @private
168
- #
169
174
  # @param structure [Hash] An input hash.
170
- # @param charset [String] The charset.
175
+ # @param charset [String] The charset of the original encoded JSON document
176
+ # if known. Used to provide default content types.
177
+ # @param data_decoder [#decode_data] Optional data field decoder, used for
178
+ # non-JSON content types.
171
179
  # @return [CloudEvents::Event]
172
180
  #
173
- def decode_hash_structure structure, charset = nil
181
+ def decode_hash_structure structure, charset: nil, data_decoder: nil
174
182
  spec_version = structure["specversion"].to_s
175
183
  case spec_version
176
184
  when "0.3"
177
185
  decode_hash_structure_v0 structure, charset
178
186
  when /^1(\.|$)/
179
- decode_hash_structure_v1 structure, charset
187
+ decode_hash_structure_v1 structure, charset, spec_version, data_decoder
180
188
  else
181
189
  raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
182
190
  end
@@ -186,24 +194,28 @@ module CloudEvents
186
194
  # Encode a single event to a hash data structure with keys and types
187
195
  # conforming to the JSON envelope.
188
196
  #
189
- # @private
190
- #
191
197
  # @param event [CloudEvents::Event] An input event.
198
+ # @param data_encoder [#encode_data] Optional data field encoder, used for
199
+ # non-JSON content types.
192
200
  # @return [String] The hash structure.
193
201
  #
194
- def encode_hash_structure event
202
+ def encode_hash_structure event, data_encoder: nil
195
203
  case event
196
204
  when Event::V0
197
205
  encode_hash_structure_v0 event
198
206
  when Event::V1
199
- encode_hash_structure_v1 event
207
+ encode_hash_structure_v1 event, data_encoder
200
208
  else
201
- raise SpecVersionError, "Unrecognized specversion: #{event.spec_version}"
209
+ raise SpecVersionError, "Unrecognized event type: #{event.class}"
202
210
  end
203
211
  end
204
212
 
205
213
  private
206
214
 
215
+ def json_content_type? content_type
216
+ content_type&.subtype_base == "json" || content_type&.subtype_format == "json"
217
+ end
218
+
207
219
  def sort_keys obj
208
220
  return obj unless obj.is_a? ::Hash
209
221
  result = {}
@@ -232,18 +244,43 @@ module CloudEvents
232
244
  Event::V0.new attributes: structure
233
245
  end
234
246
 
235
- def decode_hash_structure_v1 structure, charset
247
+ def decode_hash_structure_v1 structure, charset, spec_version, data_decoder
248
+ unless structure.key?("data") || structure.key?("data_base64")
249
+ return Event::V1.new set_attributes: structure
250
+ end
251
+ structure = structure.dup
252
+ content, content_type = retrieve_content_from_data_fields structure, charset
253
+ populate_data_fields_from_content structure, content, content_type, spec_version, data_decoder
254
+ Event::V1.new set_attributes: structure
255
+ end
256
+
257
+ def retrieve_content_from_data_fields structure, charset
236
258
  if structure.key? "data_base64"
237
- structure = structure.dup
238
- structure["data"] = ::Base64.decode64 structure.delete "data_base64"
239
- structure["datacontenttype"] ||= "application/octet-stream"
240
- elsif !structure.key? "datacontenttype"
241
- structure = structure.dup
242
- content_type = "application/json"
243
- content_type = "#{content_type}; charset=#{charset}" if charset
244
- structure["datacontenttype"] = content_type
259
+ content = ::Base64.decode64 structure.delete "data_base64"
260
+ content_type = structure["datacontenttype"] || "application/octet-stream"
261
+ else
262
+ content = structure["data"]
263
+ content_type = structure["datacontenttype"]
264
+ content_type ||= charset ? "application/json; charset=#{charset}" : "application/json"
265
+ end
266
+ [content, ContentType.new(content_type)]
267
+ end
268
+
269
+ def populate_data_fields_from_content structure, content, content_type, spec_version, data_decoder
270
+ if json_content_type? content_type
271
+ structure["data_encoded"] = ::JSON.dump content
272
+ structure["data"] = content
273
+ else
274
+ structure["data_encoded"] = content = content.to_s
275
+ result = data_decoder&.decode_data spec_version: spec_version, content: content, content_type: content_type
276
+ if result
277
+ structure["data"] = result[:data]
278
+ content_type = result[:content_type]
279
+ else
280
+ structure.delete "data"
281
+ end
245
282
  end
246
- Event::V1.new attributes: structure
283
+ structure["datacontenttype"] = content_type
247
284
  end
248
285
 
249
286
  def encode_hash_structure_v0 event
@@ -252,17 +289,40 @@ module CloudEvents
252
289
  structure
253
290
  end
254
291
 
255
- def encode_hash_structure_v1 event
292
+ def encode_hash_structure_v1 event, data_encoder
256
293
  structure = event.to_h
257
- data = structure["data"]
258
- if data.is_a?(::String) && data.encoding == ::Encoding::ASCII_8BIT
259
- structure.delete "data"
260
- structure["data_base64"] = ::Base64.encode64 data
261
- structure["datacontenttype"] ||= "application/octet-stream"
294
+ return structure unless structure.key?("data") || structure.key?("data_encoded")
295
+ content_type = event.data_content_type
296
+ if content_type.nil? || json_content_type?(content_type)
297
+ encode_data_fields_for_json_content structure, event
262
298
  else
263
- structure["datacontenttype"] ||= "application/json"
299
+ encode_data_fields_for_other_content structure, event, data_encoder
264
300
  end
265
301
  structure
266
302
  end
303
+
304
+ def encode_data_fields_for_json_content structure, event
305
+ structure["data"] = ::JSON.parse event.data unless event.data_decoded?
306
+ structure.delete "data_encoded"
307
+ structure["datacontenttype"] ||= "application/json"
308
+ end
309
+
310
+ def encode_data_fields_for_other_content structure, event, data_encoder
311
+ data_encoded = structure.delete "data_encoded"
312
+ unless data_encoded
313
+ result = data_encoder&.encode_data spec_version: event.spec_version,
314
+ data: event.data,
315
+ content_type: event.data_content_type
316
+ raise UnsupportedFormatError, "Unable to encode data of media type #{event.data_content_type}" unless result
317
+ data_encoded = result[:content]
318
+ structure["datacontenttype"] = result[:content_type].to_s
319
+ end
320
+ if data_encoded.encoding == ::Encoding::ASCII_8BIT
321
+ structure["data_base64"] = ::Base64.encode64 data_encoded
322
+ structure.delete "data"
323
+ else
324
+ structure["data"] = data_encoded
325
+ end
326
+ end
267
327
  end
268
328
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+
6
+ module CloudEvents
7
+ ##
8
+ # An data encoder/decoder for text content types. This handles any media type
9
+ # of the form `text/*` or `application/octet-stream`, and passes strings
10
+ # through as-is.
11
+ #
12
+ class TextFormat
13
+ # @private
14
+ UNSPECIFIED = ::Object.new.freeze
15
+
16
+ ##
17
+ # Trivially decode an event data string using text format.
18
+ # See {CloudEvents::Format#decode_data} for a general description.
19
+ #
20
+ # Expects `:content` and `:content_type` arguments, and will decline the
21
+ # request unless all three are provided.
22
+ #
23
+ # If decoding succeeded, returns a hash with the following keys:
24
+ #
25
+ # * `:data` (Object) The payload object to set as the `data` attribute.
26
+ # * `:content_type` ({CloudEvents::ContentType}) The content type to be set
27
+ # as the `datacontenttype` attribute.
28
+ #
29
+ # @param content [String] Serialized content to decode.
30
+ # @param content_type [CloudEvents::ContentType] The input content type.
31
+ # @return [Hash] if accepting the request.
32
+ # @return [nil] if declining the request.
33
+ #
34
+ def decode_data content: nil, content_type: nil, **_other_kwargs
35
+ return nil unless content
36
+ return nil unless text_content_type? content_type
37
+ { data: content.to_s, content_type: content_type }
38
+ end
39
+
40
+ ##
41
+ # Trivially an event data object using text format.
42
+ # See {CloudEvents::Format#encode_data} for a general description.
43
+ #
44
+ # Expects `:data` and `:content_type` arguments, and will decline the
45
+ # request unless all three are provided.
46
+ # The `:data` object will be converted to a string if it is not already a
47
+ # string.
48
+ #
49
+ # If decoding succeeded, returns a hash with the following keys:
50
+ #
51
+ # * `:content` (String) The serialized form of the data.
52
+ # * `:content_type` ({CloudEvents::ContentType}) The content type for the
53
+ # output.
54
+ #
55
+ # @param data [Object] A data object to encode.
56
+ # @param content_type [CloudEvents::ContentType] The input content type
57
+ # @return [Hash] if accepting the request.
58
+ # @return [nil] if declining the request.
59
+ #
60
+ def encode_data data: UNSPECIFIED, content_type: nil, **_other_kwargs
61
+ return nil if data == UNSPECIFIED
62
+ return nil unless text_content_type? content_type
63
+ { content: data.to_s, content_type: content_type }
64
+ end
65
+
66
+ private
67
+
68
+ def text_content_type? content_type
69
+ content_type&.media_type == "text" ||
70
+ content_type&.media_type == "application" && content_type&.subtype == "octet-stream"
71
+ end
72
+ end
73
+ end
@@ -5,5 +5,5 @@ module CloudEvents
5
5
  # Version of the Ruby CloudEvents SDK
6
6
  # @return [String]
7
7
  #
8
- VERSION = "0.5.1"
8
+ VERSION = "0.6.0"
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud_events
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-28 00:00:00.000000000 Z
11
+ date: 2021-08-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The official Ruby implementation of the CloudEvents Specification. Provides
14
14
  data types for events, and HTTP/JSON bindings for marshalling and unmarshalling
@@ -35,15 +35,16 @@ files:
35
35
  - lib/cloud_events/format.rb
36
36
  - lib/cloud_events/http_binding.rb
37
37
  - lib/cloud_events/json_format.rb
38
+ - lib/cloud_events/text_format.rb
38
39
  - lib/cloud_events/version.rb
39
40
  homepage: https://github.com/cloudevents/sdk-ruby
40
41
  licenses:
41
42
  - Apache-2.0
42
43
  metadata:
43
- changelog_uri: https://cloudevents.github.io/sdk-ruby/v0.5.1/file.CHANGELOG.html
44
+ changelog_uri: https://cloudevents.github.io/sdk-ruby/v0.6.0/file.CHANGELOG.html
44
45
  source_code_uri: https://github.com/cloudevents/sdk-ruby
45
46
  bug_tracker_uri: https://github.com/cloudevents/sdk-ruby/issues
46
- documentation_uri: https://cloudevents.github.io/sdk-ruby/v0.5.1
47
+ documentation_uri: https://cloudevents.github.io/sdk-ruby/v0.6.0
47
48
  post_install_message:
48
49
  rdoc_options: []
49
50
  require_paths: