cloud_events 0.3.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7449de71a0d20b1c7fa726bbb850080c977299ebb92c3c52ecc04c3acaa65a0c
4
- data.tar.gz: 60c0bfa47f39b3c4a10d12122c57d2ee2b26e75f4a82bc0622033275a5866946
3
+ metadata.gz: 036b0516dd1cbb3d6f2c4855809facc6edb4ac994b00355056182a704a072306
4
+ data.tar.gz: 4eccf559900a089b4a86ce269a30bd604dd4bcad3973ad5ffbe69735acb2a7e7
5
5
  SHA512:
6
- metadata.gz: ba15c5d5fd8fd432b39a861297c57f04c659f0329783ad8a02f30c60f839836438df4371937d4d063f14bef326e45741140690be854dc2f8865c3defbb948074
7
- data.tar.gz: 05cca16a9b8436a8cf7e7a23189d6873fbc322dbd177cd5d71886081a2a116d75ccb2215bc8c112a013976d769691ab0be96ab41b2d16e4acb57948c9aa7b8fc
6
+ metadata.gz: b0d255359ad545ec1f0898f437b2b220e72cc8766de1f59e0534c71c4863fd093b410e9350048506d8b99a22235d23aef9b9c4ca0fee757242229cdd283faaf2
7
+ data.tar.gz: 84a043396ad6cd11e6cd05fd7ce6bdd2993fcede29d2fc780c88419dc2bbd99d230c573b8c237f53940e033fe42449c0976e85c9842fef6dd829e798ecd4daa2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.7.0 / 2022-01-14
4
+
5
+ * HttpBinding#probable_event? returns false if the request method is GET or HEAD.
6
+ * HttpBinding#decode_event raises NotCloudEventError if the request method is GET or HEAD.
7
+ * Fixed a NoMethodError if nil content was passed to the ContentType constructor.
8
+
9
+ ### v0.6.0 / 2021-08-23
10
+
11
+ 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.
12
+
13
+ * 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.
14
+ * 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.)
15
+ * Fixed various inconsistencies in the data encoding behavior of `JsonFormat` and `HttpBinding`.
16
+ * Support passing a data content encoder/decoder into `JsonFormat#encode_event` and `JsonFormat#decode_event`.
17
+ * Provided `TextFormat` to handle media types with trivial encoding.
18
+ * Provided `Format::Multi` to handle checking a series of encoders/decoders.
19
+
20
+ ### v0.5.1 / 2021-06-28
21
+
22
+ * ADDED: Add HttpBinding#probable_event?
23
+ * FIXED: Fixed a NoMethodError when a format declined to decode an http request
24
+
25
+ ### v0.5.0 / 2021-06-28
26
+
27
+ This is a significant update that provides several important spec-related and usability fixes. Some of the behavioral changes are breaking, so to preserve compatibility, new methods were added and old methods deprecated, particularly in the HttpBinding class. Additionally, the formatter interface has been simplified and expanded to support payload formatting.
28
+
29
+ * CHANGED: Deprecated HttpBinding#decode_rack_env and replaced with HttpBinding#decode_event. (The old method remains for backward compatibility, but will be removed in version 1.0). The new decode_event method has the following differences:
30
+ * decode_event raises NotCloudEventError (rather than returning nil) if given an HTTP request that does not seem to have been intended as a CloudEvent.
31
+ * decode_event takes an allow_opaque argument that, when set to true, returns a CloudEvents::Event::Opaque (rather than raising an exception) if given a structured event with a format not known by the SDK. Opaque event objects cannot have their fields inspected, but can be reserialized for retransmission to another event handler.
32
+ * decode_event in binary content mode now parses JSON content-types and exposes the data attribute as a JSON value.
33
+ * CHANGED: Deprecated the HttpBinding encoding entrypoints (encode_structured_content, encode_batched_content, and encode_binary_content) and replaced with a single encode_event entrypoint that handles all cases via the structured_format argument. (The old methods remain for backward compatibility, but will be removed in version 1.0). In addition, the new encode_event method has the following differences:
34
+ * encode_event in binary content mode now interprets a string-valued data attribute as a JSON string and serializes it (i.e. wraps it in quotes) if the data_content_type has a JSON format. This is for compatibility with the behavior of the JSON structured mode which always treats the data attribute as a JSON value if the data_content_type indicates JSON.
35
+ * CHANGED: The JsonFormat class interface was reworked to be more generic, combine the structured and batched calls, and add calls to handle data payloads. A Format module has been added to specify the interface used by JsonFormat and future formatters. (Breaking change of internal interfaces)
36
+ * CHANGED: Renamed HttpContentError to UnsupportedFormatError to better reflect the specific issue being reported, and to eliminate the coupling to http. The old name remains aliased to the new name, but is deprecated and will be removed in version 1.0.
37
+ * CHANGED: If format-driven parsing (e.g. parsing a JSON document) fails, a FormatSyntaxError will be raised instead of, for example, a JSON-specific error. The lower-level parser error can still be accessed from the exception's "cause".
38
+ * FIXED: JsonFormat now sets the datacontenttype attribute explicitly to "application/json" if it isn't otherwise set.
39
+
40
+ ### v0.4.0 / 2021-05-26
41
+
42
+ * ADDED: ContentType can take an optional default charset
43
+ * FIXED: Binary HTTP format parses quoted tokens according to RFC 7230 section 3.2.6
44
+ * FIXED: When encoding structured events for HTTP transport, the content-type now includes the charset
45
+
3
46
  ### v0.3.1 / 2021-04-25
4
47
 
5
48
  * FIXED: Fixed exception when decoding from a rack source that uses InputWrapper
data/README.md CHANGED
@@ -13,7 +13,7 @@ Features:
13
13
  JSON Batch Format.
14
14
  * Support for sending and receiving CloudEvents via HTTP Bindings.
15
15
  * Supports the [CloudEvent 0.3](https://github.com/cloudevents/spec/tree/v0.3)
16
- and [CloudEvents 1.0](https://github.com/cloudevents/spec/tree/v1.0)
16
+ and [CloudEvents 1.0](https://github.com/cloudevents/spec/tree/v1.0.1)
17
17
  specifications.
18
18
  * Extensible to additional formats and protocol bindings, and future
19
19
  specification versions.
@@ -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.2"
38
+ gem "cloud_events", "~> 0.6"
39
39
  gem "sinatra", "~> 2.0"
40
40
  ```
41
41
 
@@ -47,7 +47,7 @@ require "cloud_events"
47
47
  cloud_events_http = CloudEvents::HttpBinding.default
48
48
 
49
49
  post "/" do
50
- event = cloud_events_http.decode_rack_env request.env
50
+ event = cloud_events_http.decode_event request.env
51
51
  logger.info "Received CloudEvent: #{event.to_h}"
52
52
  end
53
53
  ```
@@ -59,7 +59,7 @@ A simple Ruby script that sends a CloudEvent:
59
59
  ```ruby
60
60
  # examples/client/Gemfile
61
61
  source "https://rubygems.org"
62
- gem "cloud_events", "~> 0.2"
62
+ gem "cloud_events", "~> 0.6"
63
63
  ```
64
64
 
65
65
  ```ruby
@@ -69,14 +69,16 @@ require "net/http"
69
69
  require "uri"
70
70
 
71
71
  data = { message: "Hello, CloudEvents!" }
72
- event = CloudEvents::Event.create spec_version: "1.0",
73
- id: "1234-1234-1234",
74
- source: "/mycontext",
75
- type: "com.example.someevent",
76
- data: data
72
+ event = CloudEvents::Event.create \
73
+ spec_version: "1.0",
74
+ id: "1234-1234-1234",
75
+ source: "/mycontext",
76
+ type: "com.example.someevent",
77
+ data_content_type: "application/json",
78
+ data: data
77
79
 
78
80
  cloud_events_http = CloudEvents::HttpBinding.default
79
- headers, body = cloud_events_http.encode_binary_content event
81
+ headers, body = cloud_events_http.encode_event event
80
82
  Net::HTTP.post URI("http://localhost:4567"), body, headers
81
83
  ```
82
84
 
@@ -21,16 +21,18 @@ module CloudEvents
21
21
  # Parse the given header value.
22
22
  #
23
23
  # @param string [String] Content-Type header value in RFC 2045 format
24
+ # @param default_charset [String] Optional. The charset to use if none is
25
+ # specified. Defaults to `us-ascii`.
24
26
  #
25
- def initialize string
26
- @string = string
27
+ def initialize string, default_charset: nil
28
+ @string = string.to_s
27
29
  @media_type = "text"
28
30
  @subtype_base = @subtype = "plain"
29
31
  @subtype_format = nil
30
32
  @params = []
31
- @charset = "us-ascii"
33
+ @charset = default_charset || "us-ascii"
32
34
  @error_message = nil
33
- parse consume_comments string.strip
35
+ parse consume_comments @string.strip
34
36
  @canonical_string = "#{@media_type}/#{@subtype}" +
35
37
  @params.map { |k, v| "; #{k}=#{maybe_quote v}" }.join
36
38
  full_freeze
@@ -8,21 +8,53 @@ module CloudEvents
8
8
  end
9
9
 
10
10
  ##
11
- # Errors indicating unsupported or incorrectly formatted HTTP content or
12
- # headers.
11
+ # An error raised when a protocol binding was asked to decode a CloudEvent
12
+ # from input data, but does not believe that the data was intended to be a
13
+ # CloudEvent. For example, the HttpBinding might raise this exception if
14
+ # given a request that has neither the requisite headers for binary content
15
+ # mode, nor an appropriate content-type for structured content mode.
13
16
  #
14
- class HttpContentError < CloudEventsError
17
+ class NotCloudEventError < CloudEventsError
15
18
  end
16
19
 
17
20
  ##
18
- # Errors indicating an unsupported or incorrect spec version.
21
+ # An error raised when a protocol binding was asked to decode a CloudEvent
22
+ # from input data, and the data appears to be a CloudEvent, but was encoded
23
+ # in a format that is not supported. Some protocol bindings can be configured
24
+ # to return a {CloudEvents::Event::Opaque} object instead of raising this
25
+ # error.
26
+ #
27
+ class UnsupportedFormatError < CloudEventsError
28
+ end
29
+
30
+ ##
31
+ # An error raised when a protocol binding was asked to decode a CloudEvent
32
+ # from input data, and the data appears to be intended as a CloudEvent, but
33
+ # has unrecoverable format or syntax errors. This error _may_ have a `cause`
34
+ # such as a `JSON::ParserError` with additional information.
35
+ #
36
+ class FormatSyntaxError < CloudEventsError
37
+ end
38
+
39
+ ##
40
+ # An error raised when a `specversion` is set to a value not recognized or
41
+ # supported by the SDK.
19
42
  #
20
43
  class SpecVersionError < CloudEventsError
21
44
  end
22
45
 
23
46
  ##
24
- # Errors related to CloudEvent attributes.
47
+ # An error raised when a malformed CloudEvents attribute is encountered,
48
+ # often because a required attribute is missing, or a value does not match
49
+ # the attribute's type specification.
25
50
  #
26
51
  class AttributeError < CloudEventsError
27
52
  end
53
+
54
+ ##
55
+ # Alias of UnsupportedFormatError, for backward compatibility.
56
+ #
57
+ # @deprecated Will be removed in version 1.0. Use {UnsupportedFormatError}.
58
+ #
59
+ HttpContentError = UnsupportedFormatError
28
60
  end
@@ -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
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudEvents
4
+ module Event
5
+ ##
6
+ # This object represents opaque event data that arrived in structured
7
+ # content mode but was not in a recognized format. It may represent a
8
+ # single event or a batch of events.
9
+ #
10
+ # The event data is retained in a form that can be reserialized (in a
11
+ # structured cotent mode in the same format) but cannot otherwise be
12
+ # inspected.
13
+ #
14
+ # This object is immutable, and Ractor-shareable on Ruby 3.
15
+ #
16
+ class Opaque
17
+ ##
18
+ # Create an opaque object wrapping the given content and a content type.
19
+ #
20
+ # @param content [String] The opaque serialized event data.
21
+ # @param content_type [CloudEvents::ContentType,nil] The content type,
22
+ # or `nil` if there is no content type.
23
+ # @param batch [boolean] Whether this represents a batch. If set to `nil`
24
+ # or not provided, the value will be inferred from the content type
25
+ # if possible, or otherwise set to `nil` indicating not known.
26
+ #
27
+ def initialize content, content_type, batch: nil
28
+ @content = content.freeze
29
+ @content_type = content_type
30
+ if batch.nil? && content_type&.media_type == "application"
31
+ case content_type.subtype_base
32
+ when "cloudevents"
33
+ batch = false
34
+ when "cloudevents-batch"
35
+ batch = true
36
+ end
37
+ end
38
+ @batch = batch
39
+ freeze
40
+ end
41
+
42
+ ##
43
+ # The opaque serialized event data
44
+ #
45
+ # @return [String]
46
+ #
47
+ attr_reader :content
48
+
49
+ ##
50
+ # The content type, or `nil` if there is no content type.
51
+ #
52
+ # @return [CloudEvents::ContentType,nil]
53
+ #
54
+ attr_reader :content_type
55
+
56
+ ##
57
+ # Whether this represents a batch, or `nil` if not known.
58
+ #
59
+ # @return [boolean,nil]
60
+ #
61
+ def batch?
62
+ @batch
63
+ end
64
+
65
+ ## @private
66
+ def == other
67
+ Opaque === other &&
68
+ @content == other.content &&
69
+ @content_type == other.content_type &&
70
+ @batch == other.batch?
71
+ end
72
+ alias eql? ==
73
+
74
+ ## @private
75
+ def hash
76
+ @content.hash ^ @content_type.hash ^ @batch.hash
77
+ end
78
+ end
79
+ end
80
+ end
@@ -34,7 +34,10 @@ module CloudEvents
34
34
  # Create a new cloud event object with the given data and attributes.
35
35
  #
36
36
  # Event attributes may be presented as keyword arguments, or as a Hash
37
- # passed in via the `attributes` argument (but not both).
37
+ # passed in via the special `:set_attributes` keyword argument (but not
38
+ # both). The `:set_attributes` keyword argument is useful for passing in
39
+ # attributes whose keys are strings rather than symbols, which some
40
+ # versions of Ruby will not accept as keyword arguments.
38
41
  #
39
42
  # The following standard attributes are supported and exposed as
40
43
  # attribute methods on the object.
@@ -68,16 +71,18 @@ module CloudEvents
68
71
  # `:data` field, for example if you pass a structured hash. If this is an
69
72
  # issue, make a deep copy of objects before passing to this constructor.
70
73
  #
71
- # @param attributes [Hash] The data and attributes, as a hash.
74
+ # @param set_attributes [Hash] The data and attributes, as a hash.
75
+ # (Also available using the deprecated keyword `attributes`.)
72
76
  # @param args [keywords] The data and attributes, as keyword arguments.
73
77
  #
74
- def initialize attributes: nil, **args
75
- interpreter = FieldInterpreter.new attributes || args
78
+ def initialize set_attributes: nil, attributes: nil, **args
79
+ interpreter = FieldInterpreter.new set_attributes || attributes || args
76
80
  @spec_version = interpreter.spec_version ["specversion", "spec_version"], accept: /^0\.3$/
77
81
  @id = interpreter.string ["id"], required: true
78
82
  @source = interpreter.uri ["source"], required: true
79
83
  @type = interpreter.string ["type"], required: true
80
84
  @data = interpreter.data_object ["data"]
85
+ @data = nil if @data == FieldInterpreter::UNDEFINED
81
86
  @data_content_encoding = interpreter.string ["datacontentencoding", "data_content_encoding"]
82
87
  @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
83
88
  @schema_url = interpreter.uri ["schemaurl", "schema_url"]
@@ -98,7 +103,7 @@ module CloudEvents
98
103
  #
99
104
  def with **changes
100
105
  attributes = @attributes.merge changes
101
- V0.new attributes: attributes
106
+ V0.new set_attributes: attributes
102
107
  end
103
108
 
104
109
  ##
@@ -14,10 +14,29 @@ module CloudEvents
14
14
  # This object represents a complete CloudEvent, including the event data
15
15
  # and context attributes. It supports the standard required and optional
16
16
  # attributes defined in CloudEvents V1.0, and arbitrary extension
17
- # attributes. All attribute values can be obtained (in their string form)
17
+ # attributes.
18
+ #
19
+ # Values for most attributes can be obtained in their encoded string form
18
20
  # via the {Event::V1#[]} method. Additionally, standard attributes have
19
- # their own accessor methods that may return typed objects (such as
20
- # `DateTime` for the `time` attribute).
21
+ # their own accessor methods that may return decoded Ruby objects (such as
22
+ # a `DateTime` object for the `time` attribute).
23
+ #
24
+ # The `data` attribute is treated specially because it is subject to
25
+ # arbitrary encoding governed by the `datacontenttype` attribute. Data is
26
+ # expressed in two related fields: {Event::V1#data} and
27
+ # {Event::V1#data_encoded}. The former, `data`, _may_ be an arbitrary Ruby
28
+ # object representing the decoded form of the data (for example, a Hash for
29
+ # most JSON-formatted data.) The latter, `data_encoded`, _must_, if
30
+ # present, be a Ruby String object representing the encoded string or
31
+ # byte array form of the data.
32
+ #
33
+ # When the CloudEvents Ruby SDK encodes an event for transmission, it will
34
+ # use the `data_encoded` field if present. Otherwise, it will attempt to
35
+ # encode the `data` field using any available encoder that recognizes the
36
+ # content-type. Currently, text and JSON types are supported. If the type
37
+ # is not supported, event encoding may fail. It is thus recommended that
38
+ # applications provide a `data_encoded` string, if the `data` object is
39
+ # nontrivially encoded.
21
40
  #
22
41
  # This object is immutable, and Ractor-shareable on Ruby 3. The data and
23
42
  # attribute values can be retrieved but not modified. To obtain an event
@@ -33,8 +52,13 @@ module CloudEvents
33
52
  ##
34
53
  # Create a new cloud event object with the given data and attributes.
35
54
  #
55
+ # ### Specifying event attributes
56
+ #
36
57
  # Event attributes may be presented as keyword arguments, or as a Hash
37
- # passed in via the `attributes` argument (but not both).
58
+ # passed in via the special `:set_attributes` keyword argument (but not
59
+ # both). The `:set_attributes` keyword argument is useful for passing in
60
+ # attributes whose keys are strings rather than symbols, which some
61
+ # versions of Ruby will not accept as keyword arguments.
38
62
  #
39
63
  # The following standard attributes are supported and exposed as
40
64
  # attribute methods on the object.
@@ -45,11 +69,15 @@ module CloudEvents
45
69
  # * **:source** [`String`, `URI`] - _required_ - The event `source`
46
70
  # field.
47
71
  # * **:type** [`String`] - _required_ - The event `type` field.
48
- # * **:data** [`Object`] - _optional_ - The data associated with the
49
- # event (i.e. the `data` field).
72
+ # * **:data** [`Object`] - _optional_ - The "decoded" Ruby object form
73
+ # of the event `data` field, if known. (e.g. a Hash representing a
74
+ # JSON document)
75
+ # * **:data_encoded** [`String`] - _optional_ - The "encoded" string
76
+ # form of the event `data` field, if known. This should be set along
77
+ # with the `data_content_type`.
50
78
  # * **:data_content_type** (or **:datacontenttype**) [`String`,
51
- # {ContentType}] - _optional_ - The content-type for the data, if
52
- # the data is a string (i.e. the event `datacontenttype` field.)
79
+ # {ContentType}] - _optional_ - The content-type for the encoded data
80
+ # (i.e. the event `datacontenttype` field.)
53
81
  # * **:data_schema** (or **:dataschema**) [`String`, `URI`] -
54
82
  # _optional_ - The event `dataschema` field.
55
83
  # * **:subject** [`String`] - _optional_ - The event `subject` field.
@@ -65,16 +93,63 @@ module CloudEvents
65
93
  # `:data` field, for example if you pass a structured hash. If this is an
66
94
  # issue, make a deep copy of objects before passing to this constructor.
67
95
  #
68
- # @param attributes [Hash] The data and attributes, as a hash.
96
+ # ### Specifying payload data
97
+ #
98
+ # Typically you should provide _both_ the `:data` and `:data_encoded`
99
+ # fields, the former representing the decoded (Ruby object) form of the
100
+ # data, and the second providing a hint to formatters and protocol
101
+ # bindings for how to seralize the data. In this case, the {#data} and
102
+ # {#data_encoded} methods will return the corresponding values, and
103
+ # {#data_decoded?} will return true to indicate that {#data} represents
104
+ # the decoded form.
105
+ #
106
+ # If you provide _only_ the `:data` field, omitting `:data_encoded`, then
107
+ # the value is expected to represent the decoded (Ruby object) form of
108
+ # the data. The {#data} method will return this decoded value, and
109
+ # {#data_decoded?} will return true. The {#data_encoded} method will
110
+ # return nil.
111
+ # When serializing such an event, it will be up to the formatter or
112
+ # protocol binding to encode the data. This means serialization _could_
113
+ # fail if the formatter does not understand the data's content type.
114
+ # Omitting `:data_encoded` is common if the content type is JSON related
115
+ # (e.g. `application/json`) and the event is being encoded in JSON
116
+ # structured format, because the data encoding is trivial. This form can
117
+ # also be used when the content type is `text/*`, for which encoding is
118
+ # also trivial.
119
+ #
120
+ # If you provide _only_ the `:data_encoded` field, omitting `:data`, then
121
+ # the value is expected to represent the encoded (string) form of the
122
+ # data. The {#data_encoded} method will return this value. Additionally,
123
+ # the {#data} method will return the same _encoded_ value, and
124
+ # {#data_decoded?} will return false.
125
+ # Event objects of this form may be returned from a protocol binding when
126
+ # it decodes an event with a `datacontenttype` that it does not know how
127
+ # to interpret. Applications should query {#data_decoded?} to determine
128
+ # whether the {#data} method returns encoded or decoded data.
129
+ #
130
+ # If you provide _neither_ `:data` nor `:data_encoded`, the event will
131
+ # have no payload data. Both {#data} and {#data_encoded} will return nil,
132
+ # and {#data_decoded?} will return false. (Additionally, {#data?} will
133
+ # return false to signal the absence of any data.)
134
+ #
135
+ # @param set_attributes [Hash] The data and attributes, as a hash.
136
+ # (Also available using the deprecated keyword `attributes`.)
69
137
  # @param args [keywords] The data and attributes, as keyword arguments.
70
138
  #
71
- def initialize attributes: nil, **args
72
- interpreter = FieldInterpreter.new attributes || args
139
+ def initialize set_attributes: nil, attributes: nil, **args
140
+ interpreter = FieldInterpreter.new set_attributes || attributes || args
73
141
  @spec_version = interpreter.spec_version ["specversion", "spec_version"], accept: /^1(\.|$)/
74
142
  @id = interpreter.string ["id"], required: true
75
143
  @source = interpreter.uri ["source"], required: true
76
144
  @type = interpreter.string ["type"], required: true
145
+ @data_encoded = interpreter.string ["data_encoded"], allow_empty: true
77
146
  @data = interpreter.data_object ["data"]
147
+ if @data == FieldInterpreter::UNDEFINED
148
+ @data = @data_encoded
149
+ @data_decoded = false
150
+ else
151
+ @data_decoded = true
152
+ end
78
153
  @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
79
154
  @data_schema = interpreter.uri ["dataschema", "data_schema"]
80
155
  @subject = interpreter.string ["subject"]
@@ -93,8 +168,14 @@ module CloudEvents
93
168
  # @return [FunctionFramework::CloudEvents::Event]
94
169
  #
95
170
  def with **changes
96
- attributes = @attributes.merge changes
97
- V1.new attributes: attributes
171
+ changes = Utils.keys_to_strings changes
172
+ attributes = @attributes.dup
173
+ if changes.key?("data") || changes.key?("data_encoded")
174
+ attributes.delete "data"
175
+ attributes.delete "data_encoded"
176
+ end
177
+ attributes.merge! changes
178
+ V1.new set_attributes: attributes
98
179
  end
99
180
 
100
181
  ##
@@ -160,19 +241,61 @@ module CloudEvents
160
241
  alias specversion spec_version
161
242
 
162
243
  ##
163
- # The event-specific data, or `nil` if there is no data.
244
+ # The event `data` field, or `nil` if there is no data.
245
+ #
246
+ # This may return the data as an encoded string _or_ as a decoded Ruby
247
+ # object. The {#data_decoded?} method specifies whether the `data` value
248
+ # is decoded or encoded.
249
+ #
250
+ # In most cases, {#data} returns a decoded value, unless the event was
251
+ # received from a source that could not decode the content. For example,
252
+ # most protocol bindings understand how to decode JSON, so an event
253
+ # received with a {#data_content_type} of `application/json` will usually
254
+ # return a decoded object (usually a Hash) from {#data}.
164
255
  #
165
- # Data may be one of the following types:
166
- # * Binary data, represented by a `String` using the `ASCII-8BIT`
167
- # encoding.
168
- # * A string in some other encoding such as `UTF-8` or `US-ASCII`.
169
- # * Any JSON data type, such as a Boolean, Integer, Array, Hash, or
170
- # `nil`.
256
+ # See also {#data_encoded} and {#data_decoded?}.
171
257
  #
172
- # @return [Object]
258
+ # @return [Object] if containing decoded data
259
+ # @return [String] if containing encoded data
260
+ # @return [nil] if there is no data
173
261
  #
174
262
  attr_reader :data
175
263
 
264
+ ##
265
+ # The encoded string representation of the data, i.e. its raw form used
266
+ # when encoding an event for transmission. This may be `nil` if there is
267
+ # no data, or if the encoded form is not known.
268
+ #
269
+ # See also {#data}.
270
+ #
271
+ # @return [String,nil]
272
+ #
273
+ attr_reader :data_encoded
274
+
275
+ ##
276
+ # Indicates whether the {#data} field returns decoded data.
277
+ #
278
+ # @return [true] if {#data} returns a decoded Ruby object
279
+ # @return [false] if {#data} returns an encoded string or if the event
280
+ # has no data.
281
+ #
282
+ def data_decoded?
283
+ @data_decoded
284
+ end
285
+
286
+ ##
287
+ # Indicates whether the data field is present. If there is no data,
288
+ # {#data} will return `nil`, and {#data_decoded?} will return false.
289
+ #
290
+ # Generally, if there is no data, the {#data_content_type} field should
291
+ # also be absent, but this is not enforced.
292
+ #
293
+ # @return [boolean]
294
+ #
295
+ def data?
296
+ !@data.nil? || @data_decoded
297
+ end
298
+
176
299
  ##
177
300
  # The optional `datacontenttype` field as a {CloudEvents::ContentType}
178
301
  # object, or `nil` if the field is absent.
@@ -3,7 +3,7 @@
3
3
  require "date"
4
4
  require "uri"
5
5
 
6
- require "cloud_events/event/field_interpreter"
6
+ require "cloud_events/event/opaque"
7
7
  require "cloud_events/event/v0"
8
8
  require "cloud_events/event/v1"
9
9