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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f26316991786d4688dc35bab52d91d7df30c11fa573b5e5e9678b095d3e2047
4
- data.tar.gz: 9b900e5156d42e4acdae4a8312bc25b866202f738f84ed9e264815fb2635ef42
3
+ metadata.gz: 57eed57ea92eee1e51f3285c883f0f757a87f8379b7316fe5cd1eed46e1782fc
4
+ data.tar.gz: c039b2139d3692727dffcbe715b390460445d4c8b5728e2c4fbe2c59db3030c8
5
5
  SHA512:
6
- metadata.gz: 75180c4a931148fff3490084a4a43bacbcc65089812a18b397f9781f4e5e0899db5acde266f27bbf25f325c23d18c987abb4a1671125cb8db049b77da4b05a59
7
- data.tar.gz: f7f95a82b95ab9e7b6f4f4dd16f455dac9f3ea71549401b6bc6b781b6b011741f4e9b59ef97fb444c2b0fbec718eb0f8f92895f1a2a7adbd8f404e590c22d124
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
- @server = ::Puma::Server.new @app
86
- @server.min_threads = @config.min_threads
87
- @server.max_threads = @config.max_threads
88
- @server.leak_stack_on_error = @config.show_error_details?
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
- "Content-Type" => content_type,
381
- "Content-Length" => string.bytesize
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
- function = Testing.current_registry[name]
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
- when nil
144
- raise "Unknown function name #{name}"
145
- else
146
- raise "Function #{name} is not an HTTP function"
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
- function = Testing.current_registry[name]
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
@@ -17,5 +17,5 @@ module FunctionsFramework
17
17
  # Version of the Ruby Functions Framework
18
18
  # @return [String]
19
19
  #
20
- VERSION = "1.2.0".freeze
20
+ VERSION = "1.4.0".freeze
21
21
  end
@@ -115,9 +115,9 @@ module FunctionsFramework
115
115
  attr_accessor :logger
116
116
 
117
117
  ##
118
- # Define a function that response to HTTP requests.
118
+ # Define a function that responds to HTTP requests.
119
119
  #
120
- # You must provide a name for the function, and a block that implemets the
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 implemets the
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.2.0
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: 2022-08-25 00:00:00.000000000 Z
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: 6.a
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: 6.a
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.2.0/file.CHANGELOG.html
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.2.0
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.3.14
128
+ rubygems_version: 3.4.2
123
129
  signing_key:
124
130
  specification_version: 4
125
131
  summary: Functions Framework for Ruby