functions_framework 1.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/functions_framework/function.rb +24 -1
- data/lib/functions_framework/registry.rb +23 -0
- data/lib/functions_framework/server.rb +61 -6
- data/lib/functions_framework/testing.rb +49 -15
- data/lib/functions_framework/version.rb +1 -1
- data/lib/functions_framework.rb +33 -3
- metadata +15 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57eed57ea92eee1e51f3285c883f0f757a87f8379b7316fe5cd1eed46e1782fc
|
4
|
+
data.tar.gz: c039b2139d3692727dffcbe715b390460445d4c8b5728e2c4fbe2c59db3030c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1369b3334e1605956829e6077fe7d76f5f49155985598d2098928cc6a54d4f58b1252f8e6971cfd3784e104ca15162ec1e37fc0e80f2d0eadf60ad5698d7657
|
7
|
+
data.tar.gz: 9fa3b2e12ff0ad195ad0dd808c147ad30adf9eeb8ac25a2990ebddb11ef8945f80b8091455415cb0462fbc334e3eacc77afeb55175a01a713bbc2f808a3018b8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 1.4.0 (2023-06-16)
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* implement typed function signature ([#158](https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues/158))
|
8
|
+
|
9
|
+
### 1.3.0 (2023-04-05)
|
10
|
+
|
11
|
+
#### Features
|
12
|
+
|
13
|
+
* Support for Puma 6 and Rack 3
|
14
|
+
|
3
15
|
### 1.2.0 (2022-08-25)
|
4
16
|
|
5
17
|
* Update minimum Ruby version to 2.6
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Functions Framework for Ruby [![Documentation](https://img.shields.io/badge/docs-FunctionsFramework-red.svg)](https://googlecloudplatform.github.io/functions-framework-ruby/latest) [![Gem Version](https://badge.fury.io/rb/functions_framework.svg)](https://badge.fury.io/rb/functions_framework)
|
1
|
+
# Functions Framework for Ruby [![Documentation](https://img.shields.io/badge/docs-FunctionsFramework-red.svg)](https://googlecloudplatform.github.io/functions-framework-ruby/latest) [![Gem Version](https://badge.fury.io/rb/functions_framework.svg)](https://badge.fury.io/rb/functions_framework) ![Security Scorecard](https://api.securityscorecards.dev/projects/github.com/GoogleCloudPlatform/functions-framework-ruby/badge)
|
2
2
|
|
3
3
|
An open source framework for writing lightweight, portable Ruby functions that
|
4
4
|
run in a serverless environment. Functions written to this Framework will run
|
@@ -70,6 +70,23 @@ module FunctionsFramework
|
|
70
70
|
new name, :http, callable: callable, &block
|
71
71
|
end
|
72
72
|
|
73
|
+
##
|
74
|
+
# Create a new Typed function definition.
|
75
|
+
#
|
76
|
+
# @param name [String] The function name
|
77
|
+
# @param callable [Class,#call] A callable object or class.
|
78
|
+
# @param request_class [#decode_json] A class that can be read from JSON.
|
79
|
+
# @param block [Proc] The function code as a block.
|
80
|
+
# @return [FunctionsFramework::Function]
|
81
|
+
#
|
82
|
+
def self.typed name, request_class: nil, callable: nil, &block
|
83
|
+
if request_class && !(request_class.respond_to? :decode_json)
|
84
|
+
raise ::ArgumentError, "Type does not implement 'decode_json' class method"
|
85
|
+
end
|
86
|
+
|
87
|
+
new name, :typed, callable: callable, request_class: request_class, &block
|
88
|
+
end
|
89
|
+
|
73
90
|
##
|
74
91
|
# Create a new CloudEvents function definition.
|
75
92
|
#
|
@@ -102,9 +119,10 @@ module FunctionsFramework
|
|
102
119
|
# @param callable [Class,#call] A callable object or class.
|
103
120
|
# @param block [Proc] The function code as a block.
|
104
121
|
#
|
105
|
-
def initialize name, type, callable: nil, &block
|
122
|
+
def initialize name, type, callable: nil, request_class: nil, &block
|
106
123
|
@name = name
|
107
124
|
@type = type
|
125
|
+
@request_class = request_class
|
108
126
|
@callable = @callable_class = nil
|
109
127
|
if callable.respond_to? :call
|
110
128
|
@callable = callable
|
@@ -129,6 +147,11 @@ module FunctionsFramework
|
|
129
147
|
#
|
130
148
|
attr_reader :type
|
131
149
|
|
150
|
+
##
|
151
|
+
# @return [#decode_json] The class for the request parameter. Only used for typed functions.
|
152
|
+
#
|
153
|
+
attr_reader :request_class
|
154
|
+
|
132
155
|
##
|
133
156
|
# Populate the given globals hash with this function's info.
|
134
157
|
#
|
@@ -81,6 +81,29 @@ module FunctionsFramework
|
|
81
81
|
self
|
82
82
|
end
|
83
83
|
|
84
|
+
##
|
85
|
+
# Add a Typed function to the registry.
|
86
|
+
#
|
87
|
+
# You must provide a name for the function, and a block that implements the
|
88
|
+
# function. The block should take a single `Hash` argument which will be the
|
89
|
+
# JSON decoded request payload. It should return a `Hash` response which
|
90
|
+
# will be JSON encoded and written to the response.
|
91
|
+
#
|
92
|
+
# @param name [String] The function name.
|
93
|
+
# @param request_class [#decode_json] An optional class which will be used
|
94
|
+
# to decode the request.
|
95
|
+
# @param block [Proc] The function code as a proc
|
96
|
+
# @return [self]
|
97
|
+
#
|
98
|
+
def add_typed name, request_class: nil, &block
|
99
|
+
name = name.to_s
|
100
|
+
@mutex.synchronize do
|
101
|
+
raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
|
102
|
+
@functions[name] = Function.typed name, request_class: request_class, &block
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
84
107
|
##
|
85
108
|
# Add a CloudEvent function to the registry.
|
86
109
|
#
|
@@ -54,6 +54,8 @@ module FunctionsFramework
|
|
54
54
|
HttpApp.new function, globals, @config
|
55
55
|
when :cloud_event
|
56
56
|
EventApp.new function, globals, @config
|
57
|
+
when :typed
|
58
|
+
TypedApp.new function, globals, @config
|
57
59
|
else
|
58
60
|
raise "Unrecognized function type: #{function.type}"
|
59
61
|
end
|
@@ -82,10 +84,21 @@ module FunctionsFramework
|
|
82
84
|
def start
|
83
85
|
synchronize do
|
84
86
|
unless running?
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
87
|
+
# Puma >= 6.0 interprets these settings from options
|
88
|
+
options = {
|
89
|
+
min_threads: @config.min_threads,
|
90
|
+
max_threads: @config.max_threads,
|
91
|
+
environment: @config.show_error_details? ? "development" : "production"
|
92
|
+
}
|
93
|
+
# Puma::Events.stdio for Puma < 6.0; otherwise nil for Puma >= 6.0
|
94
|
+
events = ::Puma::Events.stdio if ::Puma::Events.respond_to? :stdio
|
95
|
+
@server = ::Puma::Server.new @app, events, options
|
96
|
+
if @server.respond_to? :min_threads=
|
97
|
+
# Puma < 6.0 sets server attributes for these settings
|
98
|
+
@server.min_threads = @config.min_threads
|
99
|
+
@server.max_threads = @config.max_threads
|
100
|
+
@server.leak_stack_on_error = @config.show_error_details?
|
101
|
+
end
|
89
102
|
@server.binder.add_tcp_listener @config.bind_addr, @config.port
|
90
103
|
@config.logger.info "FunctionsFramework: Serving function #{@function.name.inspect} " \
|
91
104
|
"on port #{@config.port}..."
|
@@ -377,8 +390,8 @@ module FunctionsFramework
|
|
377
390
|
content_type = "#{content_type}; charset=#{string.encoding.name.downcase}"
|
378
391
|
end
|
379
392
|
headers = {
|
380
|
-
"
|
381
|
-
"
|
393
|
+
"content-type" => content_type,
|
394
|
+
"content-length" => string.bytesize
|
382
395
|
}
|
383
396
|
[status, headers, [string]]
|
384
397
|
end
|
@@ -394,6 +407,11 @@ module FunctionsFramework
|
|
394
407
|
string_response message, 500
|
395
408
|
end
|
396
409
|
|
410
|
+
def bad_request message
|
411
|
+
message = "Bad Request" unless @config.show_error_details?
|
412
|
+
string_response message, 400
|
413
|
+
end
|
414
|
+
|
397
415
|
def flush_streams
|
398
416
|
$stdout.flush
|
399
417
|
$stderr.flush
|
@@ -425,6 +443,43 @@ module FunctionsFramework
|
|
425
443
|
end
|
426
444
|
end
|
427
445
|
|
446
|
+
## @private
|
447
|
+
class TypedApp < AppBase
|
448
|
+
def initialize function, globals, config
|
449
|
+
super config
|
450
|
+
@function = function
|
451
|
+
@globals = globals
|
452
|
+
end
|
453
|
+
|
454
|
+
def call env
|
455
|
+
return notfound_response if excluded_path? env
|
456
|
+
begin
|
457
|
+
logger = env[::Rack::RACK_LOGGER] ||= @config.logger
|
458
|
+
request = ::Rack::Request.new env
|
459
|
+
logger.info "FunctionsFramework: Handling Typed #{request.request_method} request"
|
460
|
+
|
461
|
+
begin
|
462
|
+
req = if @function.request_class
|
463
|
+
request_class.decode_json request.body.read.to_s
|
464
|
+
else
|
465
|
+
JSON.parse request.body.read.to_s
|
466
|
+
end
|
467
|
+
rescue JSON::ParserError => e
|
468
|
+
return bad_request e.message
|
469
|
+
end
|
470
|
+
|
471
|
+
res = @function.call req, globals: @globals, logger: logger
|
472
|
+
return string_response res.to_json, 200, content_type: "application/json" if res
|
473
|
+
|
474
|
+
string_response "", 204
|
475
|
+
rescue ::StandardError => e
|
476
|
+
interpret_response e
|
477
|
+
end
|
478
|
+
ensure
|
479
|
+
flush_streams
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
428
483
|
## @private
|
429
484
|
class EventApp < AppBase
|
430
485
|
def initialize function, globals, config
|
@@ -134,16 +134,42 @@ module FunctionsFramework
|
|
134
134
|
#
|
135
135
|
def call_http name, request, globals: nil, logger: nil
|
136
136
|
globals ||= run_startup_tasks name, logger: logger, lenient: true
|
137
|
-
|
138
|
-
case function&.type
|
139
|
-
when :http
|
137
|
+
Testing.call :http, name, readable_name: "HTTP" do |function|
|
140
138
|
Testing.interpret_response do
|
141
139
|
function.call request, globals: globals, logger: logger
|
142
140
|
end
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Call the given Typed function for testing. The underlying function must
|
146
|
+
# be of type `:typed`. Returns the Rack response.
|
147
|
+
#
|
148
|
+
# By default, the startup tasks will be run for the given function if they
|
149
|
+
# have not already been run. You can, however, disable running startup
|
150
|
+
# tasks by providing an explicit globals hash.
|
151
|
+
#
|
152
|
+
# By default, the {FunctionsFramework.logger} will be used, but you can
|
153
|
+
# override that by providing your own logger. In particular, to disable
|
154
|
+
# logging, you can pass `Logger.new(nil)`.
|
155
|
+
#
|
156
|
+
# @param name [String] The name of the function to call
|
157
|
+
# @param request [Rack::Request] The Rack request to send
|
158
|
+
# @param globals [Hash] Do not run startup tasks, and instead provide the
|
159
|
+
# globals directly. Optional.
|
160
|
+
# @param logger [Logger] Use the given logger instead of the Functions
|
161
|
+
# Framework's global logger. Optional.
|
162
|
+
# @return [Rack::Response]
|
163
|
+
#
|
164
|
+
def call_typed name, request, globals: nil, logger: nil
|
165
|
+
globals ||= run_startup_tasks name, logger: logger, lenient: true
|
166
|
+
Testing.call :typed, name, readable_name: "Typed" do |function|
|
167
|
+
Testing.interpret_response do
|
168
|
+
config = FunctionsFramework::Server::Config.new
|
169
|
+
config.logger = logger
|
170
|
+
app = FunctionsFramework::Server::TypedApp.new function, globals, config
|
171
|
+
app.call request.env
|
172
|
+
end
|
147
173
|
end
|
148
174
|
end
|
149
175
|
|
@@ -169,15 +195,9 @@ module FunctionsFramework
|
|
169
195
|
#
|
170
196
|
def call_event name, event, globals: nil, logger: nil
|
171
197
|
globals ||= run_startup_tasks name, logger: logger, lenient: true
|
172
|
-
|
173
|
-
case function&.type
|
174
|
-
when :cloud_event
|
198
|
+
Testing.call :cloud_event, name, readable_name: "CloudEvent" do |function|
|
175
199
|
function.call event, globals: globals, logger: logger
|
176
200
|
nil
|
177
|
-
when nil
|
178
|
-
raise "Unknown function name #{name}"
|
179
|
-
else
|
180
|
-
raise "Function #{name} is not a CloudEvent function"
|
181
201
|
end
|
182
202
|
end
|
183
203
|
|
@@ -316,6 +336,20 @@ module FunctionsFramework
|
|
316
336
|
end
|
317
337
|
end
|
318
338
|
|
339
|
+
## @private
|
340
|
+
def call type, name, readable_name: nil
|
341
|
+
readable_name ||= type
|
342
|
+
function = Testing.current_registry[name]
|
343
|
+
case function&.type
|
344
|
+
when type
|
345
|
+
yield function
|
346
|
+
when nil
|
347
|
+
raise "Unknown function name #{name}"
|
348
|
+
else
|
349
|
+
raise "Function #{name} is not a #{readable_name} function"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
319
353
|
## @private
|
320
354
|
def interpret_response
|
321
355
|
response =
|
@@ -367,8 +401,8 @@ module FunctionsFramework
|
|
367
401
|
::Rack::QUERY_STRING => url.query,
|
368
402
|
::Rack::SERVER_NAME => url.host,
|
369
403
|
::Rack::SERVER_PORT => url.port,
|
404
|
+
::Rack::SERVER_PROTOCOL => "HTTP/1.1",
|
370
405
|
::Rack::RACK_URL_SCHEME => url.scheme,
|
371
|
-
::Rack::RACK_VERSION => ::Rack::VERSION,
|
372
406
|
::Rack::RACK_LOGGER => ::FunctionsFramework.logger,
|
373
407
|
::Rack::RACK_INPUT => ::StringIO.new,
|
374
408
|
::Rack::RACK_ERRORS => ::StringIO.new
|
data/lib/functions_framework.rb
CHANGED
@@ -115,9 +115,9 @@ module FunctionsFramework
|
|
115
115
|
attr_accessor :logger
|
116
116
|
|
117
117
|
##
|
118
|
-
# Define a function that
|
118
|
+
# Define a function that responds to HTTP requests.
|
119
119
|
#
|
120
|
-
# You must provide a name for the function, and a block that
|
120
|
+
# You must provide a name for the function, and a block that implements the
|
121
121
|
# function. The block should take a single `Rack::Request` argument. It
|
122
122
|
# should return one of the following:
|
123
123
|
# * A standard 3-element Rack response array. See
|
@@ -142,10 +142,40 @@ module FunctionsFramework
|
|
142
142
|
self
|
143
143
|
end
|
144
144
|
|
145
|
+
## Define a Typed function that responds to HTTP requests.
|
146
|
+
#
|
147
|
+
# You must provide a name for the function, and a block that implements the
|
148
|
+
# function. The block should take a single argument representing the request
|
149
|
+
# payload. If a `request_type` is provided, the argument object will be of
|
150
|
+
# the given decoded type; otherwise, it will be a JSON hash. The block
|
151
|
+
# should return a JSON hash or an object that implements `#to_json`.
|
152
|
+
#
|
153
|
+
# ## Example
|
154
|
+
# FunctionsFramework.typed "my-sum-function" do |add_request|
|
155
|
+
# {sum: add_request["num1"] + add_response["num2"]}
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# ## Example with Type
|
159
|
+
# FunctionsFramework.typed "identity",
|
160
|
+
# request_class: MyCustomType do |custom_type|
|
161
|
+
# custom_type
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# @param name [String] The function name. Defaults to {DEFAULT_TARGET}
|
165
|
+
# @param request_class [#decode_json] An optional class which will be used to
|
166
|
+
# decode the request if it implements a `decode_json` static method.
|
167
|
+
# @param block [Proc] The function code as a proc @return [self]
|
168
|
+
# @return [self]
|
169
|
+
#
|
170
|
+
def typed name = DEFAULT_TARGET, request_class: nil, &block
|
171
|
+
global_registry.add_typed name, request_class: request_class, &block
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
145
175
|
##
|
146
176
|
# Define a function that responds to CloudEvents.
|
147
177
|
#
|
148
|
-
# You must provide a name for the function, and a block that
|
178
|
+
# You must provide a name for the function, and a block that implements the
|
149
179
|
# function. The block should take one argument: the event object of type
|
150
180
|
# [`CloudEvents::Event`](https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event).
|
151
181
|
# Any return value is ignored.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: functions_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.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:
|
11
|
+
date: 2023-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cloud_events
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: 4.3.0
|
40
40
|
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
42
|
+
version: 7.a
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,21 +49,27 @@ dependencies:
|
|
49
49
|
version: 4.3.0
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
52
|
+
version: 7.a
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: rack
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- - "
|
57
|
+
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: '2.1'
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 4.a
|
60
63
|
type: :runtime
|
61
64
|
prerelease: false
|
62
65
|
version_requirements: !ruby/object:Gem::Requirement
|
63
66
|
requirements:
|
64
|
-
- - "
|
67
|
+
- - ">="
|
65
68
|
- !ruby/object:Gem::Version
|
66
69
|
version: '2.1'
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 4.a
|
67
73
|
description: The Functions Framework is an open source framework for writing lightweight,
|
68
74
|
portable Ruby functions that run in a serverless environment. Functions written
|
69
75
|
to this Framework will run on Google Cloud Functions, Google Cloud Run, or any other
|
@@ -100,10 +106,10 @@ homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
|
|
100
106
|
licenses:
|
101
107
|
- Apache-2.0
|
102
108
|
metadata:
|
103
|
-
changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.
|
109
|
+
changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.4.0/file.CHANGELOG.html
|
104
110
|
source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
|
105
111
|
bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
|
106
|
-
documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.
|
112
|
+
documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.4.0
|
107
113
|
post_install_message:
|
108
114
|
rdoc_options: []
|
109
115
|
require_paths:
|
@@ -119,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
125
|
- !ruby/object:Gem::Version
|
120
126
|
version: '0'
|
121
127
|
requirements: []
|
122
|
-
rubygems_version: 3.
|
128
|
+
rubygems_version: 3.4.2
|
123
129
|
signing_key:
|
124
130
|
specification_version: 4
|
125
131
|
summary: Functions Framework for Ruby
|