functions_framework 0.3.1 → 0.4.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: d09fadc9facfc650793e4c6c30f8dab5f2e5909d0d068d18e0da0bd8a748115d
4
- data.tar.gz: 7b99ab480e94f1ed4eae784121030a07106451cdc1ddd0de5e5b1d7edabbe41f
3
+ metadata.gz: cdf75f322fe9e9e78b5e65bfcc9682097606deffb46137d85b6b1cfde68d3ab8
4
+ data.tar.gz: 54f8e668875607738141963c70b1960da6c06df69083a7ef4357f66889e1a328
5
5
  SHA512:
6
- metadata.gz: b4cc30e0acb40f3de6e9a412b240c2f910a539d9b9e76085bcb701d605f657b5239b4bbeabf9a7f5e78c220ff4931ae34c7f21d028d4e472874308c91d26d997
7
- data.tar.gz: d059a1fc586dfb12bbd06a757abf46227f0dcfc54c8653365198b596045a0c34d3fd4d00fcddc331a19d5bb028f5e52f5684aa022136b1258ce59a31ec195bd2
6
+ metadata.gz: 3d6fd59bc3730333e91a049d03089169720390f5f9300420449480924800d97da118a87cdae33224644c3550fe78dfee4a07b18e32e534d9bb7645340fbdf4c9
7
+ data.tar.gz: 897cef8cc366475123250d4c9a7cea0ccd07989c9ebdc0d88d358d996c5fa727aa9bcc5eb46be286d38e72a9d2512adfbaed743975d0300502516db9e3a5fde2
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.4.0 / 2020-06-29
4
+
5
+ * Dropped the legacy and largely unsupported `:event` function type. All event functions should be of type `:cloud_event`.
6
+ * Define the object context for function execution, and include an extensible context helper.
7
+ * Support for CloudEvents with specversion 0.3.
8
+ * CloudEvents now correct percent-encodes/decodes binary headers.
9
+ * CloudEvents now includes more robust RFC 2045 parsing of the Content-Type header.
10
+ * The CloudEventsError class now properly subclasses StandardError instead of RuntimeError.
11
+ * Removed redundant `_string` accessors from event classes since raw forms are already available via `[]`.
12
+ * A variety of corrections to event-related class documentation.
13
+
3
14
  ### v0.3.1 / 2020-06-27
4
15
 
5
16
  * Fixed crash when using "return" directly in a function block.
@@ -12,7 +23,7 @@
12
23
 
13
24
  ### v0.2.1 / 2020-06-25
14
25
 
15
- * The `--signature-type` check recognizes the legacy `event` type.
26
+ * The `--signature-type` check recognizes the legacy `event` type for `:cloud_event` functions.
16
27
 
17
28
  ### v0.2.0 / 2020-06-24
18
29
 
data/README.md CHANGED
@@ -60,7 +60,7 @@ Create a `Gemfile` listing the Functions Framework as a dependency:
60
60
  ```ruby
61
61
  # Gemfile
62
62
  source "https://rubygems.org"
63
- gem "functions_framework", "~> 0.3"
63
+ gem "functions_framework", "~> 0.4"
64
64
  ```
65
65
 
66
66
  Create a file called `app.rb` and include the following code. This defines a
@@ -64,7 +64,7 @@ Create a `Gemfile` listing the Functions Framework as a dependency:
64
64
  ```ruby
65
65
  # Gemfile
66
66
  source "https://rubygems.org"
67
- gem "functions_framework", "~> 0.3"
67
+ gem "functions_framework", "~> 0.4"
68
68
  ```
69
69
 
70
70
  Create a file called `app.rb` and include the following code. This defines a
@@ -30,7 +30,7 @@ that returns a simple message in the HTTP response body:
30
30
  ```ruby
31
31
  require "functions_framework"
32
32
 
33
- FunctionsFramework.http("hello") do |request|
33
+ FunctionsFramework.http "hello" do |request|
34
34
  # Return the response body.
35
35
  "Hello, world!\n"
36
36
  end
@@ -51,7 +51,7 @@ request information in the response:
51
51
  ```ruby
52
52
  require "functions_framework"
53
53
 
54
- FunctionsFramework.http("request_info") do |request|
54
+ FunctionsFramework.http "request_info_example" do |request|
55
55
  # Include some request info in the response body.
56
56
  "Received #{request.method} from #{request.url}!\n"
57
57
  end
@@ -66,7 +66,7 @@ hosting environment.
66
66
  ```ruby
67
67
  require "functions_framework"
68
68
 
69
- FunctionsFramework.http("logging_example") do |request|
69
+ FunctionsFramework.http "logging_example" do |request|
70
70
  # Log some request info.
71
71
  request.logger.info "I received #{request.method} from #{request.url}!"
72
72
  # A simple response body.
@@ -106,10 +106,19 @@ framework such as Ruby on Rails, you may want to consider a solution such as
106
106
  Google Cloud Run that is tailored to larger applications. However, a lightweight
107
107
  framework such as Sinatra is sometimes useful when writing HTTP functions.
108
108
 
109
- It is easy to connect an HTTP function to a Sinatra app. Write the Sinatra app
110
- using the "modular" Sinatra interface (i.e. subclass `Sinatra::Base`), and then
111
- simply run the Sinatra app as a Rack handler from the function. Here is a basic
112
- example:
109
+ It is easy to connect an HTTP function to a Sinatra app. First, declare the
110
+ dependency on Sinatra in your `Gemfile`:
111
+
112
+ ```ruby
113
+ # Gemfile
114
+ source "https://rubygems.org"
115
+ gem "functions_framework", "~> 0.4"
116
+ gem "sinatra", "~> 2.0"
117
+ ```
118
+
119
+ Write the Sinatra app using the "modular" Sinatra interface (i.e. subclass
120
+ `Sinatra::Base`), and then run the Sinatra app directly as a Rack handler from
121
+ the function. Here is a basic example:
113
122
 
114
123
  ```ruby
115
124
  require "functions_framework"
@@ -143,20 +152,25 @@ information about it:
143
152
  ```ruby
144
153
  require "functions_framework"
145
154
 
146
- FunctionsFramework.cloud_event("hello") do |event|
155
+ FunctionsFramework.cloud_event "hello" do |event|
147
156
  FunctionsFramework.logger.info "I received an event of type #{event.type}!"
148
157
  end
149
158
  ```
150
159
 
151
- The event parameter is a
152
- [CloudEvents V1 Event](https://rubydoc.info/gems/functions_framework/FunctionsFramework/CloudEvents/Event/V1)
153
- object. You can find detailed information about the fields of a CloudEvent from
154
- the [CloudEvents spec](https://github.com/cloudevents/spec/blob/v1.0/spec.md).
160
+ The event parameter will be either a
161
+ [CloudEvents V0.3 Event](https://rubydoc.info/gems/functions_framework/FunctionsFramework/CloudEvents/Event/V0)
162
+ object ([see spec](https://github.com/cloudevents/spec/blob/v0.3/spec.md)) or a
163
+ [CloudEvents V1.0 Event](https://rubydoc.info/gems/functions_framework/FunctionsFramework/CloudEvents/Event/V1)
164
+ object ([see spec](https://github.com/cloudevents/spec/blob/v1.0/spec.md)).
155
165
 
156
166
  Some Google Cloud services send events in a legacy event format that was defined
157
167
  prior to CloudEvents. The Functions Framework will convert these legacy events
158
- to an equivalent CloudEvents type, so your function will always receive a
159
- CloudEvent object when it is sent an event from Google Cloud.
168
+ to an equivalent CloudEvents V1 type, so your function will always receive a
169
+ CloudEvent object when it is sent an event from Google Cloud. The precise
170
+ mapping between legacy events and CloudEvents is not specified in detail here,
171
+ but in general, the _data_ from the legacy event will be mapped to the `data`
172
+ field in the CloudEvent, and the _context_ from the legacy event will be mapped
173
+ to equivalent CloudEvent attributes.
160
174
 
161
175
  ## Error handling
162
176
 
@@ -175,7 +189,7 @@ HTTP response yourself. For example:
175
189
  ```ruby
176
190
  require "functions_framework"
177
191
 
178
- FunctionsFramework.http("error_reporter") do |request|
192
+ FunctionsFramework.http "error_reporter" do |request|
179
193
  begin
180
194
  raise "whoops!"
181
195
  rescue RuntimeError => e
@@ -222,7 +236,7 @@ A simple project might look like this:
222
236
  ```ruby
223
237
  # Gemfile
224
238
  source "https://rubygems.org"
225
- gem "functions_framework", "~> 0.3"
239
+ gem "functions_framework", "~> 0.4"
226
240
  ```
227
241
 
228
242
  ```ruby
@@ -230,7 +244,7 @@ gem "functions_framework", "~> 0.3"
230
244
  require "functions_framework"
231
245
  require_relative "lib/hello"
232
246
 
233
- FunctionsFramework.http("hello") do |request|
247
+ FunctionsFramework.http "hello" do |request|
234
248
  Hello.new(request).build_response
235
249
  end
236
250
  ```
@@ -238,7 +252,7 @@ end
238
252
  ```ruby
239
253
  # lib/hello.rb
240
254
  class Hello
241
- def initialize(request)
255
+ def initialize request
242
256
  @request = request
243
257
  end
244
258
 
@@ -139,17 +139,6 @@ module FunctionsFramework
139
139
  self
140
140
  end
141
141
 
142
- ##
143
- # This is an obsolete interface that defines an event function taking two
144
- # arguments (data and context) rather than one.
145
- #
146
- # @deprecated Use {FunctionsFramework.cloud_event} instead.
147
- #
148
- def event name = DEFAULT_TARGET, &block
149
- global_registry.add_event name, &block
150
- self
151
- end
152
-
153
142
  ##
154
143
  # Define a function that responds to CloudEvents.
155
144
  #
@@ -23,11 +23,13 @@ module FunctionsFramework
23
23
  # CloudEvents implementation.
24
24
  #
25
25
  # This is a Ruby implementation of the [CloudEvents](https://cloudevents.io)
26
- # [1.0 specification](https://github.com/cloudevents/spec/blob/master/spec.md).
26
+ # specification. It supports both
27
+ # [CloudEvents 0.3](https://github.com/cloudevents/spec/blob/v0.3/spec.md) and
28
+ # [CloudEvents 1.0](https://github.com/cloudevents/spec/blob/v1.0/spec.md).
27
29
  #
28
30
  module CloudEvents
29
31
  # @private
30
- SUPPORTED_SPEC_VERSIONS = ["1.0"].freeze
32
+ SUPPORTED_SPEC_VERSIONS = ["0.3", "1.0"].freeze
31
33
 
32
34
  class << self
33
35
  ##
@@ -23,29 +23,31 @@ module FunctionsFramework
23
23
  # Case-insensitive fields, such as media_type and subtype, are normalized
24
24
  # to lower case.
25
25
  #
26
+ # If parsing fails, this class will try to get as much information as it
27
+ # can, and fill the rest with defaults as recommended in RFC 2045 sec 5.2.
28
+ # In case of a parsing error, the {#error_message} field will be set.
29
+ #
26
30
  class ContentType
27
31
  ##
28
- # Parse the given header value
32
+ # Parse the given header value.
29
33
  #
30
34
  # @param string [String] Content-Type header value in RFC 2045 format
31
35
  #
32
36
  def initialize string
33
37
  @string = string
34
- # TODO: This handles simple cases but is not RFC-822 compliant.
35
- sections = string.to_s.split ";"
36
- media_type, subtype = sections.shift.split "/"
37
- subtype_prefix, subtype_format = subtype.split "+"
38
- @media_type = media_type.strip.downcase
39
- @subtype = subtype.strip.downcase
40
- @subtype_prefix = subtype_prefix.strip.downcase
41
- @subtype_format = subtype_format&.strip&.downcase
42
- @params = initialize_params sections
38
+ @media_type = "text"
39
+ @subtype_base = @subtype = "plain"
40
+ @subtype_format = nil
41
+ @params = []
42
+ @charset = "us-ascii"
43
+ @error_message = nil
44
+ parse consume_comments string.strip
43
45
  @canonical_string = "#{@media_type}/#{@subtype}" +
44
46
  @params.map { |k, v| "; #{k}=#{v}" }.join
45
47
  end
46
48
 
47
49
  ##
48
- # The original header content string
50
+ # The original header content string.
49
51
  # @return [String]
50
52
  #
51
53
  attr_reader :string
@@ -66,7 +68,7 @@ module FunctionsFramework
66
68
 
67
69
  ##
68
70
  # The entire content subtype (which could include an extension delimited
69
- # by a plus sign)
71
+ # by a plus sign).
70
72
  # @return [String]
71
73
  #
72
74
  attr_reader :subtype
@@ -75,7 +77,7 @@ module FunctionsFramework
75
77
  # The portion of the content subtype before any plus sign.
76
78
  # @return [String]
77
79
  #
78
- attr_reader :subtype_prefix
80
+ attr_reader :subtype_base
79
81
 
80
82
  ##
81
83
  # The portion of the content subtype after any plus sign, or nil if there
@@ -91,6 +93,18 @@ module FunctionsFramework
91
93
  #
92
94
  attr_reader :params
93
95
 
96
+ ##
97
+ # The charset, defaulting to "us-ascii" if none is explicitly set.
98
+ # @return [String]
99
+ #
100
+ attr_reader :charset
101
+
102
+ ##
103
+ # The error message when parsing, or `nil` if there was no error message.
104
+ # @return [String,nil]
105
+ #
106
+ attr_reader :error_message
107
+
94
108
  ##
95
109
  # An array of values for the given parameter name
96
110
  # @param key [String]
@@ -101,15 +115,6 @@ module FunctionsFramework
101
115
  @params.inject([]) { |a, (k, v)| key == k ? a << v : a }
102
116
  end
103
117
 
104
- ##
105
- # The first value of the "charset" parameter, or nil if there is no
106
- # charset.
107
- # @return [String,nil]
108
- #
109
- def charset
110
- param_values("charset").first
111
- end
112
-
113
118
  ## @private
114
119
  def == other
115
120
  other.is_a?(ContentType) && canonical_string == other.canonical_string
@@ -121,18 +126,90 @@ module FunctionsFramework
121
126
  canonical_string.hash
122
127
  end
123
128
 
129
+ ## @private
130
+ class ParseError < ::StandardError
131
+ end
132
+
124
133
  private
125
134
 
126
- def initialize_params sections
127
- params = sections.map do |s|
128
- k, v = s.split "="
129
- [k.strip.downcase, v.strip]
135
+ def parse str
136
+ @media_type, str = consume_token str, downcase: true, error_message: "Failed to parse media type"
137
+ str = consume_special str, "/"
138
+ @subtype, str = consume_token str, downcase: true, error_message: "Failed to parse subtype"
139
+ @subtype_base, @subtype_format = @subtype.split "+", 2
140
+ until str.empty?
141
+ str = consume_special str, ";"
142
+ name, str = consume_token str, downcase: true, error_message: "Faled to parse attribute name"
143
+ str = consume_special str, "=", error_message: "Failed to find value for attribute #{name}"
144
+ val, str = consume_token_or_quoted str, error_message: "Failed to parse value for attribute #{name}"
145
+ @params << [name, val]
146
+ @charset = val if name == "charset"
130
147
  end
131
- params.sort! do |(k1, v1), (k2, v2)|
132
- a = k1 <=> k2
133
- a.zero? ? v1 <=> v2 : a
148
+ rescue ParseError => e
149
+ @error_message = e.message
150
+ end
151
+
152
+ def consume_token str, downcase: false, error_message: nil
153
+ match = /^([\w!#\$%&'\*\+\.\^`\{\|\}-]+)(.*)$/.match str
154
+ raise ParseError, error_message || "Expected token" unless match
155
+ token = match[1]
156
+ token.downcase! if downcase
157
+ str = consume_comments match[2].strip
158
+ [token, str]
159
+ end
160
+
161
+ def consume_special str, expected, error_message: nil
162
+ raise ParseError, error_message || "Expected #{expected.inspect}" unless str.start_with? expected
163
+ consume_comments str[1..-1].strip
164
+ end
165
+
166
+ def consume_token_or_quoted str, error_message: nil
167
+ return consume_token str unless str.start_with? '"'
168
+ arr = []
169
+ index = 1
170
+ loop do
171
+ char = str[index]
172
+ case char
173
+ when nil
174
+ raise ParseError, error_message || "Quoted-string never finished"
175
+ when "\""
176
+ break
177
+ when "\\"
178
+ char = str[index + 1]
179
+ raise ParseError, error_message || "Quoted-string never finished" unless char
180
+ arr << char
181
+ index += 2
182
+ else
183
+ arr << char
184
+ index += 1
185
+ end
186
+ end
187
+ index += 1
188
+ str = consume_comments str[index..-1].strip
189
+ [arr.join, str]
190
+ end
191
+
192
+ def consume_comments str
193
+ return str unless str.start_with? "("
194
+ index = 1
195
+ loop do
196
+ char = str[index]
197
+ case char
198
+ when nil
199
+ raise ParseError, "Comment never finished"
200
+ when ")"
201
+ break
202
+ when "\\"
203
+ index += 2
204
+ when "("
205
+ str = consume_comments str[index..-1]
206
+ index = 0
207
+ else
208
+ index += 1
209
+ end
134
210
  end
135
- params
211
+ index += 1
212
+ consume_comments str[index..-1].strip
136
213
  end
137
214
  end
138
215
  end
@@ -17,7 +17,7 @@ module FunctionsFramework
17
17
  ##
18
18
  # Base class for all CloudEvents errors.
19
19
  #
20
- class CloudEventsError < ::RuntimeError
20
+ class CloudEventsError < ::StandardError
21
21
  end
22
22
 
23
23
  ##
@@ -15,15 +15,15 @@
15
15
  require "date"
16
16
  require "uri"
17
17
 
18
+ require "functions_framework/cloud_events/event/field_interpreter"
19
+ require "functions_framework/cloud_events/event/v0"
18
20
  require "functions_framework/cloud_events/event/v1"
19
21
 
20
22
  module FunctionsFramework
21
23
  module CloudEvents
22
24
  ##
23
- # CloudEvent object.
24
- #
25
- # An Event object represents a complete event, including both its data and
26
- # its context attributes. The following are true of all event objects:
25
+ # An Event object represents a complete CloudEvent, including both data and
26
+ # context attributes. The following are true of all event objects:
27
27
  #
28
28
  # * Event classes are defined within this module. For example, events
29
29
  # conforming to the CloudEvents 1.0 specification are of type
@@ -49,8 +49,11 @@ module FunctionsFramework
49
49
  # {CloudEvents::JsonFormat} to decode an event from JSON, or use
50
50
  # {CloudEvents::HttpBinding} to decode an event from an HTTP request.
51
51
  #
52
- # See https://github.com/cloudevents/spec/blob/master/spec.md for more
53
- # information about CloudEvents.
52
+ # See https://github.com/cloudevents/spec for more information about
53
+ # CloudEvents. The documentation for the individual event classes
54
+ # {FunctionsFramework::CloudEvents::Event::V0} and
55
+ # {FunctionsFramework::CloudEvents::Event::V1} also include links to their
56
+ # respective specifications.
54
57
  #
55
58
  module Event
56
59
  class << self
@@ -66,6 +69,8 @@ module FunctionsFramework
66
69
  #
67
70
  def create spec_version:, **kwargs
68
71
  case spec_version
72
+ when "0.3"
73
+ V0.new spec_version: spec_version, **kwargs
69
74
  when /^1(\.|$)/
70
75
  V1.new spec_version: spec_version, **kwargs
71
76
  else