functions_framework 1.3.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c6958234b00d6168c6756c7188b698d84984628d1ff6a1fcd3bbd46fbb6a876
4
- data.tar.gz: 5ec49e5ea07c8416ac04a336a8581cf71d26c4b2fd1048d4dc35db3e7c497bbc
3
+ metadata.gz: 57eed57ea92eee1e51f3285c883f0f757a87f8379b7316fe5cd1eed46e1782fc
4
+ data.tar.gz: c039b2139d3692727dffcbe715b390460445d4c8b5728e2c4fbe2c59db3030c8
5
5
  SHA512:
6
- metadata.gz: 66f952df1cc33a829a48454a49508d56c8b5026e4b145cb0931c04801b85b5f5b6109d8564c54bbf2fa34bc3882d0adc6f944e22af07547de1743b5ac3a824b3
7
- data.tar.gz: 81406729506f55f2ca5da840245f4323ca705022f877a31e28af22c41e679d5ea92f40dfca523b3e8c2a869225f5f5abe5a29aa5ff12e5e6ff0a6e9e7e0d9bea
6
+ metadata.gz: d1369b3334e1605956829e6077fe7d76f5f49155985598d2098928cc6a54d4f58b1252f8e6971cfd3784e104ca15162ec1e37fc0e80f2d0eadf60ad5698d7657
7
+ data.tar.gz: 9fa3b2e12ff0ad195ad0dd808c147ad30adf9eeb8ac25a2990ebddb11ef8945f80b8091455415cb0462fbc334e3eacc77afeb55175a01a713bbc2f808a3018b8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ### 1.3.0 (2023-04-05)
4
10
 
5
11
  #### Features
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
@@ -405,6 +407,11 @@ module FunctionsFramework
405
407
  string_response message, 500
406
408
  end
407
409
 
410
+ def bad_request message
411
+ message = "Bad Request" unless @config.show_error_details?
412
+ string_response message, 400
413
+ end
414
+
408
415
  def flush_streams
409
416
  $stdout.flush
410
417
  $stderr.flush
@@ -436,6 +443,43 @@ module FunctionsFramework
436
443
  end
437
444
  end
438
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
+
439
483
  ## @private
440
484
  class EventApp < AppBase
441
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 =
@@ -17,5 +17,5 @@ module FunctionsFramework
17
17
  # Version of the Ruby Functions Framework
18
18
  # @return [String]
19
19
  #
20
- VERSION = "1.3.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.3.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: 2023-04-06 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
@@ -106,10 +106,10 @@ homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
106
106
  licenses:
107
107
  - Apache-2.0
108
108
  metadata:
109
- changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.3.0/file.CHANGELOG.html
109
+ changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.4.0/file.CHANGELOG.html
110
110
  source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
111
111
  bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
112
- documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.3.0
112
+ documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v1.4.0
113
113
  post_install_message:
114
114
  rdoc_options: []
115
115
  require_paths: