functions_framework 0.3.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +1 -1
- data/docs/overview.md +1 -1
- data/docs/writing-functions.md +32 -18
- data/lib/functions_framework.rb +0 -11
- data/lib/functions_framework/cloud_events.rb +4 -2
- data/lib/functions_framework/cloud_events/content_type.rb +107 -30
- data/lib/functions_framework/cloud_events/errors.rb +1 -1
- data/lib/functions_framework/cloud_events/event.rb +11 -6
- data/lib/functions_framework/cloud_events/event/field_interpreter.rb +150 -0
- data/lib/functions_framework/cloud_events/event/v0.rb +236 -0
- data/lib/functions_framework/cloud_events/event/v1.rb +21 -161
- data/lib/functions_framework/cloud_events/http_binding.rb +44 -4
- data/lib/functions_framework/cloud_events/json_format.rb +64 -13
- data/lib/functions_framework/function.rb +81 -23
- data/lib/functions_framework/legacy_event_converter.rb +1 -1
- data/lib/functions_framework/registry.rb +0 -15
- data/lib/functions_framework/server.rb +7 -5
- data/lib/functions_framework/testing.rb +4 -4
- data/lib/functions_framework/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cdf75f322fe9e9e78b5e65bfcc9682097606deffb46137d85b6b1cfde68d3ab8
|
4
|
+
data.tar.gz: 54f8e668875607738141963c70b1960da6c06df69083a7ef4357f66889e1a328
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d6fd59bc3730333e91a049d03089169720390f5f9300420449480924800d97da118a87cdae33224644c3550fe78dfee4a07b18e32e534d9bb7645340fbdf4c9
|
7
|
+
data.tar.gz: 897cef8cc366475123250d4c9a7cea0ccd07989c9ebdc0d88d358d996c5fa727aa9bcc5eb46be286d38e72a9d2512adfbaed743975d0300502516db9e3a5fde2
|
data/CHANGELOG.md
CHANGED
@@ -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.
|
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
|
data/docs/overview.md
CHANGED
@@ -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.
|
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
|
data/docs/writing-functions.md
CHANGED
@@ -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
|
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
|
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
|
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.
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
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
|
152
|
-
[CloudEvents
|
153
|
-
object
|
154
|
-
|
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
|
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.
|
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
|
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
|
255
|
+
def initialize request
|
242
256
|
@request = request
|
243
257
|
end
|
244
258
|
|
data/lib/functions_framework.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@
|
39
|
-
@
|
40
|
-
|
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 :
|
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
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
211
|
+
index += 1
|
212
|
+
consume_comments str[index..-1].strip
|
136
213
|
end
|
137
214
|
end
|
138
215
|
end
|
@@ -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
|
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
|
53
|
-
#
|
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
|