cloud_events 0.7.1 → 0.8.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: 3b24efea575e52e5e9290bfba5b64f06cc52145ddc8572153b418c2d29bf8bf2
4
- data.tar.gz: dd6f1d1788846857a698c9b5152eaa7294f5d76d653b2e189762ee7ca108a89f
3
+ metadata.gz: e92a0db6d57da2784f958e81f3416ade3c4c6c1e5e7830571be444c84a1001a0
4
+ data.tar.gz: e48bad4cf6cf8797c6f5a16817f6951a1c3b2138778fa2e6779b58fd49f28f21
5
5
  SHA512:
6
- metadata.gz: 66a25968b6662e7603a2bd83b97f0b8390f05baddb36f1a4d26f68351c3ccb35596e12f8b889095186226c09b0d338c059a4d3c6f9035afe1325a8369cc714e1
7
- data.tar.gz: ce94d00dde6b272e9c6d7e26a90e923c98cccbb184acf0b4ccc2de3081c20c0ba311080a4529cb2184908ea2aec1318f88c7fc161e1b04f24189a3e6061d7155
6
+ metadata.gz: 4429473d95e6150514f796a907f74a96741ab9378e2c833ed6945fbe4f5d85fe654d0bb6502960900c349721b167f90fc62994a2525a3fd372bc7ea9468c66f6
7
+ data.tar.gz: 91f22bf6c0b20357326166d896e0036dd90ae32f66b7c233a63cb3e060abac51827b2a9d560f6324830db2b5240e436d0f942c99a41bf8a1c711a48fe6af8d80
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.8.0 / 2025-11-04
4
+
5
+ * BREAKING CHANGE: Raise AttributeError if an illegal attribute name is used
6
+ * ADDED: Require Ruby 2.7 or later
7
+ * FIXED: Improved hashing algorithm for opaque event objects
8
+ * FIXED: Removed dependency on base64 gem
9
+ * FIXED: Raise AttributeError if an illegal attribute name is used
10
+ * DOCS: Add link to the security mailing list
11
+
3
12
  ### v0.7.1 / 2023-10-04
4
13
 
5
14
  * DOCS: Governance docs per CE PR 1226
data/README.md CHANGED
@@ -218,6 +218,10 @@ for how PR reviews and approval, and our
218
218
  [Code of Conduct](https://github.com/cloudevents/spec/blob/master/community/GOVERNANCE.md#additional-information)
219
219
  information.
220
220
 
221
+ If there is a security concern with one of the CloudEvents specifications, or
222
+ with one of the project's SDKs, please send an email to
223
+ [cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io).
224
+
221
225
  ## Additional SDK Resources
222
226
 
223
227
  - [List of current active maintainers](MAINTAINERS.md)
@@ -24,7 +24,7 @@ module CloudEvents
24
24
  # @param default_charset [String] Optional. The charset to use if none is
25
25
  # specified. Defaults to `us-ascii`.
26
26
  #
27
- def initialize string, default_charset: nil
27
+ def initialize(string, default_charset: nil)
28
28
  @string = string.to_s
29
29
  @media_type = "text"
30
30
  @subtype_base = @subtype = "plain"
@@ -32,9 +32,9 @@ module CloudEvents
32
32
  @params = []
33
33
  @charset = default_charset || "us-ascii"
34
34
  @error_message = nil
35
- parse consume_comments @string.strip
35
+ parse(consume_comments(@string.strip))
36
36
  @canonical_string = "#{@media_type}/#{@subtype}" +
37
- @params.map { |k, v| "; #{k}=#{maybe_quote v}" }.join
37
+ @params.map { |k, v| "; #{k}=#{maybe_quote(v)}" }.join
38
38
  full_freeze
39
39
  end
40
40
 
@@ -102,13 +102,13 @@ module CloudEvents
102
102
  # @param key [String]
103
103
  # @return [Array<String>]
104
104
  #
105
- def param_values key
105
+ def param_values(key)
106
106
  key = key.downcase
107
107
  @params.inject([]) { |a, (k, v)| key == k ? a << v : a }
108
108
  end
109
109
 
110
110
  ## @private
111
- def == other
111
+ def ==(other)
112
112
  other.is_a?(ContentType) && canonical_string == other.canonical_string
113
113
  end
114
114
  alias eql? ==
@@ -124,16 +124,16 @@ module CloudEvents
124
124
 
125
125
  private
126
126
 
127
- def parse str
128
- @media_type, str = consume_token str, downcase: true, error_message: "Failed to parse media type"
129
- str = consume_special str, "/"
130
- @subtype, str = consume_token str, downcase: true, error_message: "Failed to parse subtype"
131
- @subtype_base, @subtype_format = @subtype.split "+", 2
127
+ def parse(str)
128
+ @media_type, str = consume_token(str, downcase: true, error_message: "Failed to parse media type")
129
+ str = consume_special(str, "/")
130
+ @subtype, str = consume_token(str, downcase: true, error_message: "Failed to parse subtype")
131
+ @subtype_base, @subtype_format = @subtype.split("+", 2)
132
132
  until str.empty?
133
- str = consume_special str, ";"
134
- name, str = consume_token str, downcase: true, error_message: "Faled to parse attribute name"
135
- str = consume_special str, "=", error_message: "Failed to find value for attribute #{name}"
136
- val, str = consume_token_or_quoted str, error_message: "Failed to parse value for attribute #{name}"
133
+ str = consume_special(str, ";")
134
+ name, str = consume_token(str, downcase: true, error_message: "Faled to parse attribute name")
135
+ str = consume_special(str, "=", error_message: "Failed to find value for attribute #{name}")
136
+ val, str = consume_token_or_quoted(str, error_message: "Failed to parse value for attribute #{name}")
137
137
  @params << [name, val]
138
138
  @charset = val if name == "charset"
139
139
  end
@@ -141,34 +141,34 @@ module CloudEvents
141
141
  @error_message = e.message
142
142
  end
143
143
 
144
- def consume_token str, downcase: false, error_message: nil
145
- match = /^([\w!#$%&'*+.\^`{|}-]+)(.*)$/.match str
146
- raise ParseError, error_message || "Expected token" unless match
144
+ def consume_token(str, downcase: false, error_message: nil)
145
+ match = /^([\w!#$%&'*+.\^`{|}-]+)(.*)$/.match(str)
146
+ raise(ParseError, error_message || "Expected token") unless match
147
147
  token = match[1]
148
148
  token.downcase! if downcase
149
- str = consume_comments match[2].strip
149
+ str = consume_comments(match[2].strip)
150
150
  [token, str]
151
151
  end
152
152
 
153
- def consume_special str, expected, error_message: nil
154
- raise ParseError, error_message || "Expected #{expected.inspect}" unless str.start_with? expected
155
- consume_comments str[1..-1].strip
153
+ def consume_special(str, expected, error_message: nil)
154
+ raise(ParseError, error_message || "Expected #{expected.inspect}") unless str.start_with?(expected)
155
+ consume_comments(str[1..].strip)
156
156
  end
157
157
 
158
- def consume_token_or_quoted str, error_message: nil
159
- return consume_token str unless str.start_with? '"'
158
+ def consume_token_or_quoted(str, error_message: nil)
159
+ return consume_token(str) unless str.start_with?('"')
160
160
  arr = []
161
161
  index = 1
162
162
  loop do
163
163
  char = str[index]
164
164
  case char
165
165
  when nil
166
- raise ParseError, error_message || "Quoted-string never finished"
166
+ raise(ParseError, error_message || "Quoted-string never finished")
167
167
  when "\""
168
168
  break
169
169
  when "\\"
170
170
  char = str[index + 1]
171
- raise ParseError, error_message || "Quoted-string never finished" unless char
171
+ raise(ParseError, error_message || "Quoted-string never finished") unless char
172
172
  arr << char
173
173
  index += 2
174
174
  else
@@ -177,34 +177,34 @@ module CloudEvents
177
177
  end
178
178
  end
179
179
  index += 1
180
- str = consume_comments str[index..-1].strip
180
+ str = consume_comments(str[index..].strip)
181
181
  [arr.join, str]
182
182
  end
183
183
 
184
- def consume_comments str
185
- return str unless str.start_with? "("
184
+ def consume_comments(str)
185
+ return str unless str.start_with?("(")
186
186
  index = 1
187
187
  loop do
188
188
  char = str[index]
189
189
  case char
190
190
  when nil
191
- raise ParseError, "Comment never finished"
191
+ raise(ParseError, "Comment never finished")
192
192
  when ")"
193
193
  break
194
194
  when "\\"
195
195
  index += 2
196
196
  when "("
197
- str = consume_comments str[index..-1]
197
+ str = consume_comments(str[index..])
198
198
  index = 0
199
199
  else
200
200
  index += 1
201
201
  end
202
202
  end
203
203
  index += 1
204
- consume_comments str[index..-1].strip
204
+ consume_comments(str[index..].strip)
205
205
  end
206
206
 
207
- def maybe_quote str
207
+ def maybe_quote(str)
208
208
  return str if /^[\w!#$%&'*+.\^`{|}-]+$/ =~ str
209
209
  str = str.gsub("\\", "\\\\\\\\").gsub("\"", "\\\\\"")
210
210
  "\"#{str}\""
@@ -9,60 +9,61 @@ module CloudEvents
9
9
  # @private
10
10
  #
11
11
  class FieldInterpreter
12
- def initialize args
13
- @args = Utils.keys_to_strings args
12
+ def initialize(args)
13
+ @args = Utils.keys_to_strings(args)
14
14
  @attributes = {}
15
15
  end
16
16
 
17
- def finish_attributes
17
+ def finish_attributes(requires_lc_start: false)
18
18
  @args.each do |key, value|
19
+ check_attribute_name(key, requires_lc_start)
19
20
  @attributes[key.freeze] = value.to_s.freeze unless value.nil?
20
21
  end
21
22
  @args = {}
22
23
  @attributes.freeze
23
24
  end
24
25
 
25
- def string keys, required: false, allow_empty: false
26
- object keys, required: required do |value|
26
+ def string(keys, required: false, allow_empty: false)
27
+ object(keys, required: required) do |value|
27
28
  case value
28
29
  when ::String
29
- raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty? && !allow_empty
30
+ raise(AttributeError, "The #{keys.first} field cannot be empty") if value.empty? && !allow_empty
30
31
  value.freeze
31
32
  [value, value]
32
33
  else
33
- raise AttributeError, "Illegal type for #{keys.first}:" \
34
- " String expected but #{value.class} found"
34
+ raise(AttributeError, "Illegal type for #{keys.first}: " \
35
+ "String expected but #{value.class} found")
35
36
  end
36
37
  end
37
38
  end
38
39
 
39
- def uri keys, required: false
40
- object keys, required: required do |value|
40
+ def uri(keys, required: false)
41
+ object(keys, required: required) do |value|
41
42
  case value
42
43
  when ::String
43
- raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
44
+ raise(AttributeError, "The #{keys.first} field cannot be empty") if value.empty?
44
45
  begin
45
46
  [Utils.deep_freeze(::URI.parse(value)), value.freeze]
46
47
  rescue ::URI::InvalidURIError => e
47
- raise AttributeError, "Illegal format for #{keys.first}: #{e.message}"
48
+ raise(AttributeError, "Illegal format for #{keys.first}: #{e.message}")
48
49
  end
49
50
  when ::URI::Generic
50
51
  [Utils.deep_freeze(value), value.to_s.freeze]
51
52
  else
52
- raise AttributeError, "Illegal type for #{keys.first}:" \
53
- " String or URI expected but #{value.class} found"
53
+ raise(AttributeError, "Illegal type for #{keys.first}: " \
54
+ "String or URI expected but #{value.class} found")
54
55
  end
55
56
  end
56
57
  end
57
58
 
58
- def rfc3339_date_time keys, required: false
59
- object keys, required: required do |value|
59
+ def rfc3339_date_time(keys, required: false)
60
+ object(keys, required: required) do |value|
60
61
  case value
61
62
  when ::String
62
63
  begin
63
64
  [Utils.deep_freeze(::DateTime.rfc3339(value)), value.freeze]
64
65
  rescue ::Date::Error => e
65
- raise AttributeError, "Illegal format for #{keys.first}: #{e.message}"
66
+ raise(AttributeError, "Illegal format for #{keys.first}: #{e.message}")
66
67
  end
67
68
  when ::DateTime
68
69
  [Utils.deep_freeze(value), value.rfc3339.freeze]
@@ -70,44 +71,44 @@ module CloudEvents
70
71
  value = value.to_datetime
71
72
  [Utils.deep_freeze(value), value.rfc3339.freeze]
72
73
  else
73
- raise AttributeError, "Illegal type for #{keys.first}:" \
74
- " String, Time, or DateTime expected but #{value.class} found"
74
+ raise(AttributeError, "Illegal type for #{keys.first}: " \
75
+ "String, Time, or DateTime expected but #{value.class} found")
75
76
  end
76
77
  end
77
78
  end
78
79
 
79
- def content_type keys, required: false
80
- object keys, required: required do |value|
80
+ def content_type(keys, required: false)
81
+ object(keys, required: required) do |value|
81
82
  case value
82
83
  when ::String
83
- raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
84
+ raise(AttributeError, "The #{keys.first} field cannot be empty") if value.empty?
84
85
  [ContentType.new(value), value.freeze]
85
86
  when ContentType
86
87
  [value, value.to_s]
87
88
  else
88
- raise AttributeError, "Illegal type for #{keys.first}:" \
89
- " String, or ContentType expected but #{value.class} found"
89
+ raise(AttributeError, "Illegal type for #{keys.first}: " \
90
+ "String, or ContentType expected but #{value.class} found")
90
91
  end
91
92
  end
92
93
  end
93
94
 
94
- def spec_version keys, accept:
95
- object keys, required: true do |value|
95
+ def spec_version(keys, accept:)
96
+ object(keys, required: true) do |value|
96
97
  case value
97
98
  when ::String
98
- raise SpecVersionError, "Unrecognized specversion: #{value}" unless accept =~ value
99
+ raise(SpecVersionError, "Unrecognized specversion: #{value}") unless accept =~ value
99
100
  value.freeze
100
101
  [value, value]
101
102
  else
102
- raise AttributeError, "Illegal type for #{keys.first}:" \
103
- " String expected but #{value.class} found"
103
+ raise(AttributeError, "Illegal type for #{keys.first}: " \
104
+ "String expected but #{value.class} found")
104
105
  end
105
106
  end
106
107
  end
107
108
 
108
- def data_object keys, required: false
109
- object keys, required: required, allow_nil: true do |value|
110
- Utils.deep_freeze value
109
+ def data_object(keys, required: false)
110
+ object(keys, required: required, allow_nil: true) do |value|
111
+ Utils.deep_freeze(value)
111
112
  [value, value]
112
113
  end
113
114
  end
@@ -116,21 +117,28 @@ module CloudEvents
116
117
 
117
118
  private
118
119
 
119
- def object keys, required: false, allow_nil: false
120
+ def object(keys, required: false, allow_nil: false)
120
121
  value = UNDEFINED
121
122
  keys.each do |key|
122
- key_present = @args.key? key
123
- val = @args.delete key
124
- value = val if allow_nil && key_present || !allow_nil && !val.nil?
123
+ key_present = @args.key?(key)
124
+ val = @args.delete(key)
125
+ value = val if (allow_nil && key_present) || (!allow_nil && !val.nil?)
125
126
  end
126
127
  if value == UNDEFINED
127
- raise AttributeError, "The #{keys.first} field is required" if required
128
+ raise(AttributeError, "The #{keys.first} field is required") if required
128
129
  return allow_nil ? UNDEFINED : nil
129
130
  end
130
- converted, raw = yield value
131
+ converted, raw = yield(value)
131
132
  @attributes[keys.first.freeze] = raw
132
133
  converted
133
134
  end
135
+
136
+ def check_attribute_name(key, requires_lc_start)
137
+ regex = requires_lc_start ? /^[a-z][a-z0-9]*$/ : /^[a-z0-9]+$/
138
+ unless regex.match?(key)
139
+ raise(AttributeError, "Illegal key: #{key.inspect} must consist only of digits and lower-case letters")
140
+ end
141
+ end
134
142
  end
135
143
  end
136
144
  end
@@ -24,7 +24,7 @@ module CloudEvents
24
24
  # or not provided, the value will be inferred from the content type
25
25
  # if possible, or otherwise set to `nil` indicating not known.
26
26
  #
27
- def initialize content, content_type, batch: nil
27
+ def initialize(content, content_type, batch: nil)
28
28
  @content = content.freeze
29
29
  @content_type = content_type
30
30
  if batch.nil? && content_type&.media_type == "application"
@@ -63,7 +63,7 @@ module CloudEvents
63
63
  end
64
64
 
65
65
  ## @private
66
- def == other
66
+ def ==(other)
67
67
  Opaque === other &&
68
68
  @content == other.content &&
69
69
  @content_type == other.content_type &&
@@ -73,7 +73,7 @@ module CloudEvents
73
73
 
74
74
  ## @private
75
75
  def hash
76
- @content.hash ^ @content_type.hash ^ @batch.hash
76
+ [@content, @content_type, @batch].hash
77
77
  end
78
78
  end
79
79
  end
@@ -8,37 +8,37 @@ module CloudEvents
8
8
  #
9
9
  module Utils
10
10
  class << self
11
- def deep_freeze obj
11
+ def deep_freeze(obj)
12
12
  case obj
13
13
  when ::Hash
14
14
  obj.each do |key, val|
15
- deep_freeze key
16
- deep_freeze val
15
+ deep_freeze(key)
16
+ deep_freeze(val)
17
17
  end
18
18
  when ::Array
19
19
  obj.each do |val|
20
- deep_freeze val
20
+ deep_freeze(val)
21
21
  end
22
22
  else
23
23
  obj.instance_variables.each do |iv|
24
- deep_freeze obj.instance_variable_get iv
24
+ deep_freeze(obj.instance_variable_get(iv))
25
25
  end
26
26
  end
27
27
  obj.freeze
28
28
  end
29
29
 
30
- def deep_dup obj
30
+ def deep_dup(obj)
31
31
  case obj
32
32
  when ::Hash
33
- obj.each_with_object({}) { |(key, val), hash| hash[deep_dup key] = deep_dup val }
33
+ obj.each_with_object({}) { |(key, val), hash| hash[deep_dup(key)] = deep_dup(val) }
34
34
  when ::Array
35
- obj.map { |val| deep_dup val }
35
+ obj.map { |val| deep_dup(val) }
36
36
  else
37
37
  obj.dup
38
38
  end
39
39
  end
40
40
 
41
- def keys_to_strings hash
41
+ def keys_to_strings(hash)
42
42
  result = {}
43
43
  hash.each do |key, val|
44
44
  result[key.to_s] = val
@@ -75,20 +75,20 @@ module CloudEvents
75
75
  # (Also available using the deprecated keyword `attributes`.)
76
76
  # @param args [keywords] The data and attributes, as keyword arguments.
77
77
  #
78
- def initialize set_attributes: nil, attributes: nil, **args
79
- interpreter = FieldInterpreter.new set_attributes || attributes || args
80
- @spec_version = interpreter.spec_version ["specversion", "spec_version"], accept: /^0\.3$/
81
- @id = interpreter.string ["id"], required: true
82
- @source = interpreter.uri ["source"], required: true
83
- @type = interpreter.string ["type"], required: true
84
- @data = interpreter.data_object ["data"]
78
+ def initialize(set_attributes: nil, attributes: nil, **args)
79
+ interpreter = FieldInterpreter.new(set_attributes || attributes || args)
80
+ @spec_version = interpreter.spec_version(["specversion", "spec_version"], accept: /^0\.3$/)
81
+ @id = interpreter.string(["id"], required: true)
82
+ @source = interpreter.uri(["source"], required: true)
83
+ @type = interpreter.string(["type"], required: true)
84
+ @data = interpreter.data_object(["data"])
85
85
  @data = nil if @data == FieldInterpreter::UNDEFINED
86
- @data_content_encoding = interpreter.string ["datacontentencoding", "data_content_encoding"]
87
- @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
88
- @schema_url = interpreter.uri ["schemaurl", "schema_url"]
89
- @subject = interpreter.string ["subject"]
90
- @time = interpreter.rfc3339_date_time ["time"]
91
- @attributes = interpreter.finish_attributes
86
+ @data_content_encoding = interpreter.string(["datacontentencoding", "data_content_encoding"])
87
+ @data_content_type = interpreter.content_type(["datacontenttype", "data_content_type"])
88
+ @schema_url = interpreter.uri(["schemaurl", "schema_url"])
89
+ @subject = interpreter.string(["subject"])
90
+ @time = interpreter.rfc3339_date_time(["time"])
91
+ @attributes = interpreter.finish_attributes(requires_lc_start: true)
92
92
  freeze
93
93
  end
94
94
 
@@ -101,9 +101,9 @@ module CloudEvents
101
101
  # @param changes [keywords] See {#initialize} for a list of arguments.
102
102
  # @return [FunctionFramework::CloudEvents::Event]
103
103
  #
104
- def with **changes
105
- attributes = @attributes.merge changes
106
- V0.new set_attributes: attributes
104
+ def with(**changes)
105
+ attributes = @attributes.merge(changes)
106
+ V0.new(set_attributes: attributes)
107
107
  end
108
108
 
109
109
  ##
@@ -125,7 +125,7 @@ module CloudEvents
125
125
  # @param key [String,Symbol] The attribute name.
126
126
  # @return [String,nil]
127
127
  #
128
- def [] key
128
+ def [](key)
129
129
  @attributes[key.to_s]
130
130
  end
131
131
 
@@ -136,7 +136,7 @@ module CloudEvents
136
136
  # @return [Hash]
137
137
  #
138
138
  def to_h
139
- Utils.deep_dup @attributes
139
+ Utils.deep_dup(@attributes)
140
140
  end
141
141
 
142
142
  ##
@@ -225,7 +225,7 @@ module CloudEvents
225
225
  attr_reader :time
226
226
 
227
227
  ## @private
228
- def == other
228
+ def ==(other)
229
229
  other.is_a?(V0) && @attributes == other.instance_variable_get(:@attributes)
230
230
  end
231
231
  alias eql? ==
@@ -136,24 +136,24 @@ module CloudEvents
136
136
  # (Also available using the deprecated keyword `attributes`.)
137
137
  # @param args [keywords] The data and attributes, as keyword arguments.
138
138
  #
139
- def initialize set_attributes: nil, attributes: nil, **args
140
- interpreter = FieldInterpreter.new set_attributes || attributes || args
141
- @spec_version = interpreter.spec_version ["specversion", "spec_version"], accept: /^1(\.|$)/
142
- @id = interpreter.string ["id"], required: true
143
- @source = interpreter.uri ["source"], required: true
144
- @type = interpreter.string ["type"], required: true
145
- @data_encoded = interpreter.string ["data_encoded"], allow_empty: true
146
- @data = interpreter.data_object ["data"]
139
+ def initialize(set_attributes: nil, attributes: nil, **args)
140
+ interpreter = FieldInterpreter.new(set_attributes || attributes || args)
141
+ @spec_version = interpreter.spec_version(["specversion", "spec_version"], accept: /^1(\.|$)/)
142
+ @id = interpreter.string(["id"], required: true)
143
+ @source = interpreter.uri(["source"], required: true)
144
+ @type = interpreter.string(["type"], required: true)
145
+ @data_encoded = interpreter.string(["data_encoded"], allow_empty: true)
146
+ @data = interpreter.data_object(["data"])
147
147
  if @data == FieldInterpreter::UNDEFINED
148
148
  @data = @data_encoded
149
149
  @data_decoded = false
150
150
  else
151
151
  @data_decoded = true
152
152
  end
153
- @data_content_type = interpreter.content_type ["datacontenttype", "data_content_type"]
154
- @data_schema = interpreter.uri ["dataschema", "data_schema"]
155
- @subject = interpreter.string ["subject"]
156
- @time = interpreter.rfc3339_date_time ["time"]
153
+ @data_content_type = interpreter.content_type(["datacontenttype", "data_content_type"])
154
+ @data_schema = interpreter.uri(["dataschema", "data_schema"])
155
+ @subject = interpreter.string(["subject"])
156
+ @time = interpreter.rfc3339_date_time(["time"])
157
157
  @attributes = interpreter.finish_attributes
158
158
  freeze
159
159
  end
@@ -167,15 +167,15 @@ module CloudEvents
167
167
  # @param changes [keywords] See {#initialize} for a list of arguments.
168
168
  # @return [FunctionFramework::CloudEvents::Event]
169
169
  #
170
- def with **changes
171
- changes = Utils.keys_to_strings changes
170
+ def with(**changes)
171
+ changes = Utils.keys_to_strings(changes)
172
172
  attributes = @attributes.dup
173
173
  if changes.key?("data") || changes.key?("data_encoded")
174
- attributes.delete "data"
175
- attributes.delete "data_encoded"
174
+ attributes.delete("data")
175
+ attributes.delete("data_encoded")
176
176
  end
177
- attributes.merge! changes
178
- V1.new set_attributes: attributes
177
+ attributes.merge!(changes)
178
+ V1.new(set_attributes: attributes)
179
179
  end
180
180
 
181
181
  ##
@@ -197,7 +197,7 @@ module CloudEvents
197
197
  # @param key [String,Symbol] The attribute name.
198
198
  # @return [String,nil]
199
199
  #
200
- def [] key
200
+ def [](key)
201
201
  @attributes[key.to_s]
202
202
  end
203
203
 
@@ -208,7 +208,7 @@ module CloudEvents
208
208
  # @return [Hash]
209
209
  #
210
210
  def to_h
211
- Utils.deep_dup @attributes
211
+ Utils.deep_dup(@attributes)
212
212
  end
213
213
 
214
214
  ##
@@ -330,7 +330,7 @@ module CloudEvents
330
330
  attr_reader :time
331
331
 
332
332
  ## @private
333
- def == other
333
+ def ==(other)
334
334
  other.is_a?(V1) && @attributes == other.instance_variable_get(:@attributes)
335
335
  end
336
336
  alias eql? ==
@@ -58,14 +58,14 @@ module CloudEvents
58
58
  # @param spec_version [String] The required `specversion` field.
59
59
  # @param kwargs [keywords] Additional parameters for the event.
60
60
  #
61
- def create spec_version:, **kwargs
61
+ def create(spec_version:, **kwargs)
62
62
  case spec_version
63
63
  when "0.3"
64
- V0.new spec_version: spec_version, **kwargs
64
+ V0.new(spec_version: spec_version, **kwargs)
65
65
  when /^1(\.|$)/
66
- V1.new spec_version: spec_version, **kwargs
66
+ V1.new(spec_version: spec_version, **kwargs)
67
67
  else
68
- raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
68
+ raise(SpecVersionError, "Unrecognized specversion: #{spec_version}")
69
69
  end
70
70
  end
71
71
  alias new create