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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +3 -3
- data/lib/cloud_events.rb +1 -0
- data/lib/cloud_events/event/field_interpreter.rb +3 -3
- data/lib/cloud_events/event/v0.rb +10 -5
- data/lib/cloud_events/event/v1.rb +144 -21
- data/lib/cloud_events/format.rb +77 -2
- data/lib/cloud_events/http_binding.rb +106 -84
- data/lib/cloud_events/json_format.rb +95 -35
- data/lib/cloud_events/text_format.rb +73 -0
- data/lib/cloud_events/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e30cd516cae86c634b7fd69990ba1297a5e53fa20a7c5630b69f9f356c93589e
|
4
|
+
data.tar.gz: d7e6447bf17e5e18ba70153106cd8d6e3f73aa2704d9854b3f5360ccf7f869f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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.
|
62
|
+
gem "cloud_events", "~> 0.6"
|
63
63
|
```
|
64
64
|
|
65
65
|
```ruby
|
data/lib/cloud_events.rb
CHANGED
@@ -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 `
|
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
|
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
|
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.
|
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
|
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 `
|
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
|
49
|
-
#
|
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
|
52
|
-
#
|
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
|
-
#
|
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
|
-
|
97
|
-
|
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
|
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
|
-
#
|
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.
|
data/lib/cloud_events/format.rb
CHANGED
@@ -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
|
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
|
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 =
|
44
|
-
|
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
|
55
|
-
# register its encode operations. Optional. If not specified, any
|
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,
|
60
|
-
|
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 =
|
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
|
107
|
+
@event_decoders.formats.unshift formatter if decode_event
|
95
108
|
if encode_event
|
96
|
-
|
97
|
-
|
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
|
100
|
-
@data_encoders
|
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,
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
290
|
-
|
291
|
-
|
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
|
-
|
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
|
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.
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
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
|
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
|
-
|
390
|
-
|
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
|
-
|
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
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
238
|
-
structure["
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
content_type
|
243
|
-
|
244
|
-
|
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
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
structure
|
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
|
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
|
data/lib/cloud_events/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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:
|