cloud_events 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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: