cloud_events 0.4.0 → 0.5.1

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: ae042efe6c2ccd066620bf2e4f548d6f0470fff3426e92442e5f403c8e06273a
4
- data.tar.gz: f8d1fbbb00faa1d2b72db8c1e9ac016580951884e78cc6129eeec7fb8d844d5a
3
+ metadata.gz: d44f430fb349c68608ecb791c8e0727c7c6cef018022a4f28c6dcb140b201d48
4
+ data.tar.gz: d4f1755b09f71fe1817d227666762c642de19172062a7fbdc29ec2a837a7b31e
5
5
  SHA512:
6
- metadata.gz: 06a156c8448d1c7b59ef683e90ecba1d22dfbe4564574a9355ee110c31f91240137295c437933ed6038fe8e1a9e9e5b19067ef82ee11affd38c6c2f41dac2b50
7
- data.tar.gz: 9d8b7c3745261e6addd427b9b4d24b3873014f998f0f0844889315a6ea59ab3c11e44cbc25abfdffdb758357545c8c62eeca4f5d5138e6bd2179d700e1dd60c6
6
+ metadata.gz: c0f36a2c0cf43efafbda5b94a7392e5c52ff7974df002d0051013a4db1bd809d62721499c2f0c5f1712907f72dbbea6e2280532d1ac275a0aa33e96e4094eb19
7
+ data.tar.gz: c18abd09d34d4e7f51ac2f327d10ae99ab7541ee2f13afa2df46c67424e40bc2e99521f64341436858c686261cdab315945173940cd467fa55bc49ead4590096
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.5.1 / 2021-06-28
4
+
5
+ * ADDED: Add HttpBinding#probable_event?
6
+ * FIXED: Fixed a NoMethodError when a format declined to decode an http request
7
+
8
+ ### v0.5.0 / 2021-06-28
9
+
10
+ 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.
11
+
12
+ * 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:
13
+ * decode_event raises NotCloudEventError (rather than returning nil) if given an HTTP request that does not seem to have been intended as a CloudEvent.
14
+ * 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.
15
+ * decode_event in binary content mode now parses JSON content-types and exposes the data attribute as a JSON value.
16
+ * 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:
17
+ * 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.
18
+ * 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)
19
+ * 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.
20
+ * 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".
21
+ * FIXED: JsonFormat now sets the datacontenttype attribute explicitly to "application/json" if it isn't otherwise set.
22
+
3
23
  ### v0.4.0 / 2021-05-26
4
24
 
5
25
  * ADDED: ContentType can take an optional default charset
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.5"
39
39
  gem "sinatra", "~> 2.0"
40
40
  ```
41
41
 
@@ -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.5"
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
 
data/lib/cloud_events.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "cloud_events/content_type"
4
4
  require "cloud_events/errors"
5
5
  require "cloud_events/event"
6
+ require "cloud_events/format"
6
7
  require "cloud_events/http_binding"
7
8
  require "cloud_events/json_format"
8
9
 
@@ -25,7 +25,7 @@ module CloudEvents
25
25
  # specified. Defaults to `us-ascii`.
26
26
  #
27
27
  def initialize string, default_charset: nil
28
- @string = string
28
+ @string = string.to_s
29
29
  @media_type = "text"
30
30
  @subtype_base = @subtype = "plain"
31
31
  @subtype_format = nil
@@ -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
@@ -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
 
@@ -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
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+
6
+ module CloudEvents
7
+ ##
8
+ # This module documents the method signatures that may be implemented by
9
+ # formatters.
10
+ #
11
+ # A formatter is an object that implenets "structured" event encoding and
12
+ # decoding strategies for a particular format (such as JSON). In general,
13
+ # this includes four operations:
14
+ #
15
+ # * Decoding an entire event or batch of events from a input source.
16
+ # This is implemented by the {Format#decode_event} method.
17
+ # * Encoding an entire event or batch of events to an output sink.
18
+ # This is implemented by the {Format#encode_event} method.
19
+ # * Decoding an event payload (i.e. the `data` attribute) Ruby object from a
20
+ # serialized representation.
21
+ # This is implemented by the {Format#decode_data} method.
22
+ # * Encoding an event payload (i.e. the `data` attribute) Ruby object to a
23
+ # serialized representation.
24
+ # This is implemented by the {Format#encode_data} method.
25
+ #
26
+ # Each method takes a set of keyword arguments, and returns either a `Hash`
27
+ # or `nil`. A Hash indicates that the formatter understands the request and
28
+ # is returning its response. A return value of `nil` means the formatter does
29
+ # not understand the request and is declining to perform the operation. In
30
+ # such a case, it is possible that a different formatter should handle it.
31
+ #
32
+ # Both the keyword arguments recognized and the returned hash members may
33
+ # vary from formatter to formatter; similarly, the keyword arguments provided
34
+ # and the resturned hash members recognized may also vary for different
35
+ # callers. This interface will define a set of common argument and result key
36
+ # names, but both callers and formatters must gracefully handle the case of
37
+ # missing or extra information. For example, if a formatter expects a certain
38
+ # argument but does not receive it, it can assume the caller does not have
39
+ # the required information, and it may respond by returning `nil` to decline
40
+ # the request. Similarly, if a caller expects a response key but does not
41
+ # receive it, it can assume the formatter does not provide it, and it may
42
+ # respond by trying a different formatter.
43
+ #
44
+ # Additionally, any particular formatter need not implement all methods. For
45
+ # example, an event formatter would generally implement {Format#decode_event}
46
+ # and {Format#encode_event}, but might not implement {Format#decode_data} or
47
+ # {Format#encode_data}.
48
+ #
49
+ # Finally, this module itself is present primarily for documentation, and
50
+ # need not be directly included by formatter implementations.
51
+ #
52
+ module Format
53
+ ##
54
+ # Decode an event or batch from the given serialized input. This is
55
+ # typically called by a protocol binding to deserialize event data from an
56
+ # input stream.
57
+ #
58
+ # Common arguments include:
59
+ #
60
+ # * `:content` (String) Serialized content to decode. For example, it could
61
+ # be from an HTTP request body.
62
+ # * `:content_type` ({CloudEvents::ContentType}) The content type. For
63
+ # example, it could be from the `Content-Type` header of an HTTP request.
64
+ #
65
+ # The formatter must first determine whether it is able to interpret the
66
+ # given input. Typically, this is done by inspecting the `content_type`.
67
+ # If the formatter determines that it is unable to interpret the input, it
68
+ # should return `nil`. Otherwise, if the formatter determines it can decode
69
+ # the input, it should return a `Hash`. Common hash keys include:
70
+ #
71
+ # * `:event` ({CloudEvents::Event}) A single event decoded from the input.
72
+ # * `:event_batch` (Array of {CloudEvents::Event}) A batch of events
73
+ # decoded from the input.
74
+ #
75
+ # The formatter may also raise a {CloudEvents::CloudEventsError} subclass
76
+ # if it understood the request but determines that the input source is
77
+ # malformed.
78
+ #
79
+ # @param _kwargs [keywords] Arguments
80
+ # @return [Hash] if accepting the request and returning a result
81
+ # @return [nil] if declining the request.
82
+ #
83
+ def decode_event **_kwargs
84
+ nil
85
+ end
86
+
87
+ ##
88
+ # Encode an event or batch to a string. This is typically called by a
89
+ # protocol binding to serialize event data to an output stream.
90
+ #
91
+ # Common arguments include:
92
+ #
93
+ # * `:event` ({CloudEvents::Event}) A single event to encode.
94
+ # * `:event_batch` (Array of {CloudEvents::Event}) A batch of events to
95
+ # encode.
96
+ #
97
+ # The formatter must first determine whether it is able to interpret the
98
+ # given input. Typically, most formatters should be able to handle any
99
+ # event or event batch, but a specialized formatter that can handle only
100
+ # certain kinds of events may return `nil` to decline unwanted inputs.
101
+ # Otherwise, if the formatter determines it can encode the input, it should
102
+ # return a `Hash`. common hash keys include:
103
+ #
104
+ # * `:content` (String) The serialized form of the event. This might, for
105
+ # example, be written to an HTTP request body. Care should be taken to
106
+ # set the string's encoding properly. In particular, to output binary
107
+ # data, the encoding should probably be set to `ASCII_8BIT`.
108
+ # * `:content_type` ({CloudEvents::ContentType}) The content type for the
109
+ # output. This might, for example, be written to the `Content-Type`
110
+ # header of an HTTP request.
111
+ #
112
+ # The formatter may also raise a {CloudEvents::CloudEventsError} subclass
113
+ # if it understood the request but determines that the input source is
114
+ # malformed.
115
+ #
116
+ # @param _kwargs [keywords] Arguments
117
+ # @return [Hash] if accepting the request and returning a result
118
+ # @return [nil] if declining the request.
119
+ #
120
+ def encode_event **_kwargs
121
+ nil
122
+ end
123
+
124
+ ##
125
+ # Decode an event data object from string format. This is typically called
126
+ # by a protocol binding to deserialize the payload (i.e. `data` attribute)
127
+ # of an event as part of "binary content mode" decoding.
128
+ #
129
+ # Common arguments include:
130
+ #
131
+ # * `:spec_version` (String) The `specversion` of the event.
132
+ # * `:content` (String) Serialized payload to decode. For example, it could
133
+ # be from an HTTP request body.
134
+ # * `:content_type` ({CloudEvents::ContentType}) The content type. For
135
+ # example, it could be from the `Content-Type` header of an HTTP request.
136
+ #
137
+ # The formatter must first determine whether it is able to interpret the
138
+ # given input. Typically, this is done by inspecting the `content_type`.
139
+ # If the formatter determines that it is unable to interpret the input, it
140
+ # should return `nil`. Otherwise, if the formatter determines it can decode
141
+ # the input, it should return a `Hash`. Common hash keys include:
142
+ #
143
+ # * `:data` (Object) The payload object to be set as the `data` attribute
144
+ # in a {CloudEvents::Event} object.
145
+ # * `:content_type` ({CloudEvents::ContentType}) The content type to be set
146
+ # as the `datacontenttype` attribute in a {CloudEvents::Event} object.
147
+ # In many cases, this may simply be copied from the `:content_type`
148
+ # argument, but a formatter could modify it to provide corrections or
149
+ # additional information.
150
+ #
151
+ # The formatter may also raise a {CloudEvents::CloudEventsError} subclass
152
+ # if it understood the request but determines that the input source is
153
+ # malformed.
154
+ #
155
+ # @param _kwargs [keywords] Arguments
156
+ # @return [Hash] if accepting the request and returning a result
157
+ # @return [nil] if declining the request.
158
+ #
159
+ def decode_data **_kwargs
160
+ nil
161
+ end
162
+
163
+ ##
164
+ # Encode an event data object to string format. This is typically called by
165
+ # a protocol binding to serialize the payload (i.e. `data` attribute and
166
+ # corresponding `datacontenttype` attribute) of an event as part of "binary
167
+ # content mode" encoding.
168
+ #
169
+ # Common arguments include:
170
+ #
171
+ # * `:spec_version` (String) The `specversion` of the event.
172
+ # * `:data` (Object) The payload object from an event's `data` attribute.
173
+ # * `:content_type` ({CloudEvents::ContentType}) The content type from an
174
+ # event's `datacontenttype` attribute.
175
+ #
176
+ # The formatter must first determine whether it is able to interpret the
177
+ # given input. Typically, this is done by inspecting the `content_type`.
178
+ # If the formatter determines that it is unable to interpret the input, it
179
+ # should return `nil`. Otherwise, if the formatter determines it can decode
180
+ # the input, it should return a `Hash`. Common hash keys include:
181
+ #
182
+ # * `:content` (String) The serialized form of the data. This might, for
183
+ # example, be written to an HTTP request body. Care should be taken to
184
+ # set the string's encoding properly. In particular, to output binary
185
+ # data, the encoding should probably be set to `ASCII_8BIT`.
186
+ # * `:content_type` ({CloudEvents::ContentType}) The content type for the
187
+ # output. This might, for example, be written to the `Content-Type`
188
+ # header of an HTTP request.
189
+ #
190
+ # The formatter may also raise a {CloudEvents::CloudEventsError} subclass
191
+ # if it understood the request but determines that the input source is
192
+ # malformed.
193
+ #
194
+ # @param _kwargs [keywords] Arguments
195
+ # @return [Hash] if accepting the request and returning a result
196
+ # @return [nil] if declining the request.
197
+ #
198
+ def encode_data **_kwargs
199
+ nil
200
+ end
201
+ end
202
+ end