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 +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:
|