cloud_events 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 352989599792500818d6a7f57d28c131e88abd43f34eadda1a2533ef1407bf0c
4
- data.tar.gz: edb587ea833390c5ecaeccf17dcf19c17cf5347d4928473deced3830a192433b
3
+ metadata.gz: 561f1577a53c99db91573da43e0caa899add4388169a8c4784910dbfb3ae94d7
4
+ data.tar.gz: eb936cd23fbbd2b2658ec8a5c52ad5857c50b8f81019e15f341bdeb89346d037
5
5
  SHA512:
6
- metadata.gz: a45bae8560267d2f40212f193b2847c24302659699350a06def41c46d4d31a748e3459ef2073d66d9cbca5520598b834c2ddb020507cefd7db45c1b80ccb4ad9
7
- data.tar.gz: cc19771ab81fed83130ca8114685251a3c9b814490a2232dd903cd6b1d9a4271be23258bdf174723cdeabe86df5c74cba05f511066d75ed92c9a1a19a7a1d72f
6
+ metadata.gz: 0c702c7b33d247b38c9ef8c46dc0d0ef3253d5ffe5d6e8ba7ba6043a0d0f597b748c40514bbe94f0836c150d5f7336e67005f550bbabe6ecfefd73b6f9925ff0
7
+ data.tar.gz: f765051aec3c216b36eafdd082f771701d8647d7c4cead2489a61370fb598390fb5d7f43b138a7ab0af91a039d4a88e36c9f4897e91d37650220b474260c9d3d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.3.0 / 2021-03-02
4
+
5
+ * ADDED: Require Ruby 2.5 or later
6
+ * FIXED: Deep-duplicated event attributes in to_h to avoid returning frozen objects
7
+
3
8
  ### v0.2.0 / 2021-01-25
4
9
 
5
10
  * ADDED: Freeze event objects to make them Ractor-shareable
data/README.md CHANGED
@@ -17,7 +17,7 @@ Features:
17
17
  specifications.
18
18
  * Extensible to additional formats and protocol bindings, and future
19
19
  specification versions.
20
- * Compatible with Ruby 2.4 or later, or JRuby 9.2.x or later. No runtime gem
20
+ * Compatible with Ruby 2.5 or later, or JRuby 9.2.x or later. No runtime gem
21
21
  dependencies.
22
22
 
23
23
  ## Quickstart
@@ -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.1"
38
+ gem "cloud_events", "~> 0.2"
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.1"
62
+ gem "cloud_events", "~> 0.2"
63
63
  ```
64
64
 
65
65
  ```ruby
@@ -140,7 +140,7 @@ module CloudEvents
140
140
  end
141
141
 
142
142
  def consume_token str, downcase: false, error_message: nil
143
- match = /^([\w!#\$%&'\*\+\.\^`\{\|\}-]+)(.*)$/.match str
143
+ match = /^([\w!#$%&'*+.\^`{|}-]+)(.*)$/.match str
144
144
  raise ParseError, error_message || "Expected token" unless match
145
145
  token = match[1]
146
146
  token.downcase! if downcase
@@ -203,7 +203,7 @@ module CloudEvents
203
203
  end
204
204
 
205
205
  def maybe_quote str
206
- return str if /^[\w!#\$%&'\*\+\.\^`\{\|\}-]+$/ =~ str
206
+ return str if /^[\w!#$%&'*+.\^`{|}-]+$/ =~ str
207
207
  str = str.gsub("\\", "\\\\\\\\").gsub("\"", "\\\\\"")
208
208
  "\"#{str}\""
209
209
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cloud_events/event/utils"
4
+
3
5
  module CloudEvents
4
6
  module Event
5
7
  ##
6
8
  # A helper that extracts and interprets event fields from an input hash.
7
- #
8
9
  # @private
9
10
  #
10
11
  class FieldInterpreter
11
12
  def initialize args
12
- @args = keys_to_strings args
13
+ @args = Utils.keys_to_strings args
13
14
  @attributes = {}
14
15
  end
15
16
 
@@ -26,6 +27,7 @@ module CloudEvents
26
27
  case value
27
28
  when ::String
28
29
  raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
30
+ value.freeze
29
31
  [value, value]
30
32
  else
31
33
  raise AttributeError, "Illegal type for #{keys.first}:" \
@@ -40,12 +42,12 @@ module CloudEvents
40
42
  when ::String
41
43
  raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
42
44
  begin
43
- [::URI.parse(value), value]
45
+ [Utils.deep_freeze(::URI.parse(value)), value.freeze]
44
46
  rescue ::URI::InvalidURIError => e
45
47
  raise AttributeError, "Illegal format for #{keys.first}: #{e.message}"
46
48
  end
47
49
  when ::URI::Generic
48
- [value, value.to_s]
50
+ [Utils.deep_freeze(value), value.to_s.freeze]
49
51
  else
50
52
  raise AttributeError, "Illegal type for #{keys.first}:" \
51
53
  " String or URI expected but #{value.class} found"
@@ -58,15 +60,15 @@ module CloudEvents
58
60
  case value
59
61
  when ::String
60
62
  begin
61
- [::DateTime.rfc3339(value), value]
63
+ [Utils.deep_freeze(::DateTime.rfc3339(value)), value.freeze]
62
64
  rescue ::Date::Error => e
63
65
  raise AttributeError, "Illegal format for #{keys.first}: #{e.message}"
64
66
  end
65
67
  when ::DateTime
66
- [value, value.rfc3339]
68
+ [Utils.deep_freeze(value), value.rfc3339.freeze]
67
69
  when ::Time
68
70
  value = value.to_datetime
69
- [value, value.rfc3339]
71
+ [Utils.deep_freeze(value), value.rfc3339.freeze]
70
72
  else
71
73
  raise AttributeError, "Illegal type for #{keys.first}:" \
72
74
  " String, Time, or DateTime expected but #{value.class} found"
@@ -79,7 +81,7 @@ module CloudEvents
79
81
  case value
80
82
  when ::String
81
83
  raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
82
- [ContentType.new(value), value]
84
+ [ContentType.new(value), value.freeze]
83
85
  when ContentType
84
86
  [value, value.to_s]
85
87
  else
@@ -94,6 +96,7 @@ module CloudEvents
94
96
  case value
95
97
  when ::String
96
98
  raise SpecVersionError, "Unrecognized specversion: #{value}" unless accept =~ value
99
+ value.freeze
97
100
  [value, value]
98
101
  else
99
102
  raise AttributeError, "Illegal type for #{keys.first}:" \
@@ -102,8 +105,17 @@ module CloudEvents
102
105
  end
103
106
  end
104
107
 
108
+ def data_object keys, required: false
109
+ object keys, required: required, allow_nil: true do |value|
110
+ Utils.deep_freeze value
111
+ [value, value]
112
+ end
113
+ end
114
+
105
115
  UNDEFINED = ::Object.new.freeze
106
116
 
117
+ private
118
+
107
119
  def object keys, required: false, allow_nil: false
108
120
  value = UNDEFINED
109
121
  keys.each do |key|
@@ -115,44 +127,10 @@ module CloudEvents
115
127
  raise AttributeError, "The #{keys.first} field is required" if required
116
128
  return nil
117
129
  end
118
- if block_given?
119
- converted, raw = yield value
120
- deep_freeze converted
121
- else
122
- converted = raw = value
123
- end
124
- @attributes[keys.first.freeze] = raw.freeze
130
+ converted, raw = yield value
131
+ @attributes[keys.first.freeze] = raw
125
132
  converted
126
133
  end
127
-
128
- private
129
-
130
- def deep_freeze obj
131
- case obj
132
- when ::Hash
133
- obj.each do |key, val|
134
- deep_freeze key
135
- deep_freeze val
136
- end
137
- when ::Array
138
- obj.each do |val|
139
- deep_freeze val
140
- end
141
- else
142
- obj.instance_variables.each do |iv|
143
- deep_freeze obj.instance_variable_get iv
144
- end
145
- end
146
- obj.freeze
147
- end
148
-
149
- def keys_to_strings hash
150
- result = {}
151
- hash.each do |key, val|
152
- result[key.to_s] = val
153
- end
154
- result
155
- end
156
134
  end
157
135
  end
158
136
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudEvents
4
+ module Event
5
+ ##
6
+ # A variety of helper methods.
7
+ # @private
8
+ #
9
+ module Utils
10
+ class << self
11
+ def deep_freeze obj
12
+ case obj
13
+ when ::Hash
14
+ obj.each do |key, val|
15
+ deep_freeze key
16
+ deep_freeze val
17
+ end
18
+ when ::Array
19
+ obj.each do |val|
20
+ deep_freeze val
21
+ end
22
+ else
23
+ obj.instance_variables.each do |iv|
24
+ deep_freeze obj.instance_variable_get iv
25
+ end
26
+ end
27
+ obj.freeze
28
+ end
29
+
30
+ def deep_dup obj
31
+ case obj
32
+ when ::Hash
33
+ obj.each_with_object({}) { |(key, val), hash| hash[deep_dup key] = deep_dup val }
34
+ when ::Array
35
+ obj.map { |val| deep_dup val }
36
+ else
37
+ obj.dup
38
+ end
39
+ end
40
+
41
+ def keys_to_strings hash
42
+ result = {}
43
+ hash.each do |key, val|
44
+ result[key.to_s] = val
45
+ end
46
+ result
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -3,6 +3,9 @@
3
3
  require "date"
4
4
  require "uri"
5
5
 
6
+ require "cloud_events/event/field_interpreter"
7
+ require "cloud_events/event/utils"
8
+
6
9
  module CloudEvents
7
10
  module Event
8
11
  ##
@@ -43,7 +46,7 @@ module CloudEvents
43
46
  # field.
44
47
  # * **:type** [`String`] - _required_ - The event `type` field.
45
48
  # * **:data** [`Object`] - _optional_ - The data associated with the
46
- # event (i.e. the `data` field.)
49
+ # event (i.e. the `data` field).
47
50
  # * **:data_content_encoding** (or **:datacontentencoding**)
48
51
  # [`String`] - _optional_ - The content-encoding for the data (i.e.
49
52
  # the `datacontentencoding` field.)
@@ -74,7 +77,7 @@ module CloudEvents
74
77
  @id = interpreter.string ["id"], required: true
75
78
  @source = interpreter.uri ["source"], required: true
76
79
  @type = interpreter.string ["type"], required: true
77
- @data = interpreter.object ["data"], allow_nil: true
80
+ @data = interpreter.data_object ["data"]
78
81
  @data_content_encoding = interpreter.string ["datacontentencoding", "data_content_encoding"]
79
82
  @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
80
83
  @schema_url = interpreter.uri ["schemaurl", "schema_url"]
@@ -112,6 +115,8 @@ module CloudEvents
112
115
  # event["time"] # => String rfc3339 representation
113
116
  # event.time # => DateTime object
114
117
  #
118
+ # Results are also always frozen and cannot be modified in place.
119
+ #
115
120
  # @param key [String,Symbol] The attribute name.
116
121
  # @return [String,nil]
117
122
  #
@@ -121,12 +126,12 @@ module CloudEvents
121
126
 
122
127
  ##
123
128
  # Return a hash representation of this event. The returned hash is an
124
- # unfrozen copy. Modifications do not affect the original event.
129
+ # unfrozen deep copy. Modifications do not affect the original event.
125
130
  #
126
131
  # @return [Hash]
127
132
  #
128
133
  def to_h
129
- @attributes.dup
134
+ Utils.deep_dup @attributes
130
135
  end
131
136
 
132
137
  ##
@@ -3,6 +3,9 @@
3
3
  require "date"
4
4
  require "uri"
5
5
 
6
+ require "cloud_events/event/field_interpreter"
7
+ require "cloud_events/event/utils"
8
+
6
9
  module CloudEvents
7
10
  module Event
8
11
  ##
@@ -43,7 +46,7 @@ module CloudEvents
43
46
  # field.
44
47
  # * **:type** [`String`] - _required_ - The event `type` field.
45
48
  # * **:data** [`Object`] - _optional_ - The data associated with the
46
- # event (i.e. the `data` field.)
49
+ # event (i.e. the `data` field).
47
50
  # * **:data_content_type** (or **:datacontenttype**) [`String`,
48
51
  # {ContentType}] - _optional_ - The content-type for the data, if
49
52
  # the data is a string (i.e. the event `datacontenttype` field.)
@@ -71,7 +74,7 @@ module CloudEvents
71
74
  @id = interpreter.string ["id"], required: true
72
75
  @source = interpreter.uri ["source"], required: true
73
76
  @type = interpreter.string ["type"], required: true
74
- @data = interpreter.object ["data"], allow_nil: true
77
+ @data = interpreter.data_object ["data"]
75
78
  @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
76
79
  @data_schema = interpreter.uri ["dataschema", "data_schema"]
77
80
  @subject = interpreter.string ["subject"]
@@ -108,6 +111,8 @@ module CloudEvents
108
111
  # event["time"] # => String rfc3339 representation
109
112
  # event.time # => DateTime object
110
113
  #
114
+ # Results are also always frozen and cannot be modified in place.
115
+ #
111
116
  # @param key [String,Symbol] The attribute name.
112
117
  # @return [String,nil]
113
118
  #
@@ -117,12 +122,12 @@ module CloudEvents
117
122
 
118
123
  ##
119
124
  # Return a hash representation of this event. The returned hash is an
120
- # unfrozen copy. Modifications do not affect the original event.
125
+ # unfrozen deep copy. Modifications do not affect the original event.
121
126
  #
122
127
  # @return [Hash]
123
128
  #
124
129
  def to_h
125
- @attributes.dup
130
+ Utils.deep_dup @attributes
126
131
  end
127
132
 
128
133
  ##
@@ -228,21 +228,19 @@ module CloudEvents
228
228
  headers = {}
229
229
  body = nil
230
230
  event.to_h.each do |key, value|
231
- if key == "data"
231
+ case key
232
+ when "data"
232
233
  body = value
233
- elsif key == "datacontenttype"
234
+ when "datacontenttype"
234
235
  headers["Content-Type"] = value
235
236
  else
236
237
  headers["CE-#{key}"] = percent_encode value
237
238
  end
238
239
  end
239
- if body.is_a? ::String
240
- headers["Content-Type"] ||= if body.encoding == ::Encoding.ASCII_8BIT
241
- "application/octet-stream"
242
- else
243
- "text/plain; charset=#{body.encoding.name.downcase}"
244
- end
245
- elsif body.nil?
240
+ case body
241
+ when ::String
242
+ headers["Content-Type"] ||= string_content_type body
243
+ when nil
246
244
  headers.delete "Content-Type"
247
245
  else
248
246
  body = ::JSON.dump body
@@ -288,5 +286,15 @@ module CloudEvents
288
286
  end
289
287
  arr.pack "C*"
290
288
  end
289
+
290
+ private
291
+
292
+ def string_content_type str
293
+ if str.encoding == ::Encoding.ASCII_8BIT
294
+ "application/octet-stream"
295
+ else
296
+ "text/plain; charset=#{str.encoding.name.downcase}"
297
+ end
298
+ end
291
299
  end
292
300
  end
@@ -137,10 +137,9 @@ module CloudEvents
137
137
  structure = event.to_h
138
138
  data = event.data
139
139
  content_type = event.data_content_type
140
- if data.is_a?(::String) && !content_type.nil?
141
- if content_type.subtype == "json" || content_type.subtype_format == "json"
142
- structure["data"] = ::JSON.parse data rescue data
143
- end
140
+ if data.is_a?(::String) && !content_type.nil? &&
141
+ (content_type.subtype == "json" || content_type.subtype_format == "json")
142
+ structure["data"] = ::JSON.parse data rescue data
144
143
  end
145
144
  structure
146
145
  end
@@ -5,5 +5,5 @@ module CloudEvents
5
5
  # Version of the Ruby CloudEvents SDK
6
6
  # @return [String]
7
7
  #
8
- VERSION = "0.2.0"
8
+ VERSION = "0.3.0"
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud_events
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-01-25 00:00:00.000000000 Z
11
+ date: 2021-03-02 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
@@ -28,6 +28,7 @@ files:
28
28
  - lib/cloud_events/errors.rb
29
29
  - lib/cloud_events/event.rb
30
30
  - lib/cloud_events/event/field_interpreter.rb
31
+ - lib/cloud_events/event/utils.rb
31
32
  - lib/cloud_events/event/v0.rb
32
33
  - lib/cloud_events/event/v1.rb
33
34
  - lib/cloud_events/http_binding.rb
@@ -37,10 +38,10 @@ homepage: https://github.com/cloudevents/sdk-ruby
37
38
  licenses:
38
39
  - Apache-2.0
39
40
  metadata:
40
- changelog_uri: https://cloudevents.github.io/sdk-ruby/v0.2.0/file.CHANGELOG.html
41
+ changelog_uri: https://cloudevents.github.io/sdk-ruby/v0.3.0/file.CHANGELOG.html
41
42
  source_code_uri: https://github.com/cloudevents/sdk-ruby
42
43
  bug_tracker_uri: https://github.com/cloudevents/sdk-ruby/issues
43
- documentation_uri: https://cloudevents.github.io/sdk-ruby/v0.2.0
44
+ documentation_uri: https://cloudevents.github.io/sdk-ruby/v0.3.0
44
45
  post_install_message:
45
46
  rdoc_options: []
46
47
  require_paths:
@@ -49,7 +50,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
50
  requirements:
50
51
  - - ">="
51
52
  - !ruby/object:Gem::Version
52
- version: 2.4.0
53
+ version: '2.5'
53
54
  required_rubygems_version: !ruby/object:Gem::Requirement
54
55
  requirements:
55
56
  - - ">="