functions_framework 1.3.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: 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: