cloud_events 0.2.0 → 0.3.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: 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
  - - ">="