functions_framework 0.3.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,19 +23,17 @@ module FunctionsFramework
23
23
  # Decode an event from the given Rack environment hash.
24
24
  #
25
25
  # @param env [Hash] The Rack environment
26
- # @return [FunctionsFramework::CloudEvents::Event] if the request could
27
- # be converted
26
+ # @return [::CloudEvents::Event] if the request could be converted
28
27
  # @return [nil] if the event format was not recognized.
29
28
  #
30
29
  def decode_rack_env env
31
- content_type = CloudEvents::ContentType.new env["CONTENT_TYPE"]
32
- return nil unless content_type.media_type == "application" && content_type.subtype_prefix == "json"
30
+ content_type = ::CloudEvents::ContentType.new env["CONTENT_TYPE"]
31
+ return nil unless content_type.media_type == "application" && content_type.subtype_base == "json"
33
32
  input = read_input_json env["rack.input"], content_type.charset
34
33
  return nil unless input
35
- raw_context = input["context"] || input
36
- context = normalized_context raw_context
34
+ context = normalized_context input
37
35
  return nil unless context
38
- construct_cloud_event context, input["data"]
36
+ construct_cloud_event context, input["data"], content_type.charset
39
37
  end
40
38
 
41
39
  private
@@ -50,27 +48,27 @@ module FunctionsFramework
50
48
  nil
51
49
  end
52
50
 
53
- def normalized_context raw_context
54
- id = raw_context["eventId"]
55
- return nil unless id
56
- timestamp = raw_context["timestamp"]
57
- return nil unless timestamp
58
- type = raw_context["eventType"]
59
- return nil unless type
60
- service, resource = analyze_resource raw_context["resource"], type
61
- return nil unless service && resource
51
+ def normalized_context input
52
+ raw_context = input["context"]
53
+ id = raw_context&.[]("eventId") || input["eventId"]
54
+ timestamp = raw_context&.[]("timestamp") || input["timestamp"]
55
+ type = raw_context&.[]("eventType") || input["eventType"]
56
+ service, resource = analyze_resource raw_context&.[]("resource") || input["resource"]
57
+ service ||= service_from_type type
58
+ return nil unless id && timestamp && type && service && resource
62
59
  { id: id, timestamp: timestamp, type: type, service: service, resource: resource }
63
60
  end
64
61
 
65
- def analyze_resource raw_resource, type
62
+ def analyze_resource raw_resource
63
+ service = resource = nil
66
64
  case raw_resource
67
65
  when ::Hash
68
- [raw_resource["service"], raw_resource["name"]]
66
+ service = raw_resource["service"]
67
+ resource = raw_resource["name"]
69
68
  when ::String
70
- [service_from_type(type), raw_resource]
71
- else
72
- [nil, nil]
69
+ resource = raw_resource
73
70
  end
71
+ [service, resource]
74
72
  end
75
73
 
76
74
  def service_from_type type
@@ -80,19 +78,20 @@ module FunctionsFramework
80
78
  nil
81
79
  end
82
80
 
83
- def construct_cloud_event context, data
81
+ def construct_cloud_event context, data, charset
84
82
  source, subject = convert_source context[:service], context[:resource]
85
83
  type = LEGACY_TYPE_TO_CE_TYPE[context[:type]]
86
84
  return nil unless type && source
87
85
  ce_data = convert_data context[:service], data
88
- CloudEvents::Event.new id: context[:id],
89
- source: source,
90
- type: type,
91
- spec_version: "1.0",
92
- data_content_type: "application/json",
93
- data: ce_data,
94
- subject: subject,
95
- time: context[:timestamp]
86
+ content_type = "application/json; charset=#{charset}"
87
+ ::CloudEvents::Event.new id: context[:id],
88
+ source: source,
89
+ type: type,
90
+ spec_version: "1.0",
91
+ data_content_type: content_type,
92
+ data: ce_data,
93
+ subject: subject,
94
+ time: context[:timestamp]
96
95
  end
97
96
 
98
97
  def convert_source service, resource
@@ -75,27 +75,13 @@ module FunctionsFramework
75
75
  self
76
76
  end
77
77
 
78
- ##
79
- # This is an obsolete interface that defines an event function taking two
80
- # arguments (data and context) rather than one.
81
- #
82
- # @deprecated Use {Registry#add_cloud_event} instead.
83
- #
84
- def add_event name, &block
85
- name = name.to_s
86
- synchronize do
87
- raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
88
- @functions[name] = Function.new name, :event, &block
89
- end
90
- self
91
- end
92
-
93
78
  ##
94
79
  # Add a CloudEvent function to the registry.
95
80
  #
96
81
  # You must provide a name for the function, and a block that implemets the
97
82
  # function. The block should take _one_ argument: the event object of type
98
- # {FunctionsFramework::CloudEvents::Event}. Any return value is ignored.
83
+ # [`CloudEvents::Event`](https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event).
84
+ # Any return value is ignored.
99
85
  #
100
86
  # @param name [String] The function name
101
87
  # @param block [Proc] The function code as a proc
@@ -47,7 +47,7 @@ module FunctionsFramework
47
47
  case function.type
48
48
  when :http
49
49
  HttpApp.new function, @config
50
- when :event, :cloud_event
50
+ when :cloud_event
51
51
  EventApp.new function, @config
52
52
  else
53
53
  raise "Unrecognized function type: #{function.type}"
@@ -149,8 +149,12 @@ module FunctionsFramework
149
149
  ::Signal.trap "SIGINT" do
150
150
  Server.signal_enqueue "SIGINT", @config.logger, @server
151
151
  end
152
- ::Signal.trap "SIGHUP" do
153
- Server.signal_enqueue "SIGHUP", @config.logger, @server
152
+ begin
153
+ ::Signal.trap "SIGHUP" do
154
+ Server.signal_enqueue "SIGHUP", @config.logger, @server
155
+ end
156
+ rescue ::ArgumentError # rubocop:disable Lint/HandleExceptions
157
+ # Not available on all systems
154
158
  end
155
159
  @signals_installed = true
156
160
  end
@@ -340,7 +344,7 @@ module FunctionsFramework
340
344
  string_response response, "text/plain", 200
341
345
  when ::Hash
342
346
  string_response ::JSON.dump(response), "application/json", 200
343
- when CloudEvents::CloudEventsError
347
+ when ::CloudEvents::CloudEventsError
344
348
  cloud_events_error_response response
345
349
  when ::StandardError
346
350
  error_response "#{response.class}: #{response.message}\n#{response.backtrace}\n"
@@ -384,10 +388,11 @@ module FunctionsFramework
384
388
  return notfound_response if excluded_path? env
385
389
  response =
386
390
  begin
387
- logger = env["rack.logger"] = @config.logger
391
+ logger = env["rack.logger"] ||= @config.logger
388
392
  request = ::Rack::Request.new env
389
393
  logger.info "FunctionsFramework: Handling HTTP #{request.request_method} request"
390
- @function.call request
394
+ calling_context = @function.new_call logger: logger
395
+ calling_context.call request
391
396
  rescue ::StandardError => e
392
397
  e
393
398
  end
@@ -400,21 +405,21 @@ module FunctionsFramework
400
405
  def initialize function, config
401
406
  super config
402
407
  @function = function
403
- @cloud_events = CloudEvents::HttpBinding.default
408
+ @cloud_events = ::CloudEvents::HttpBinding.default
404
409
  @legacy_events = LegacyEventConverter.new
405
410
  end
406
411
 
407
412
  def call env
408
413
  return notfound_response if excluded_path? env
409
- logger = env["rack.logger"] = @config.logger
414
+ logger = env["rack.logger"] ||= @config.logger
410
415
  event = decode_event env
411
416
  response =
412
417
  case event
413
- when CloudEvents::Event
418
+ when ::CloudEvents::Event
414
419
  handle_cloud_event event, logger
415
420
  when ::Array
416
- CloudEvents::HttpContentError.new "Batched CloudEvents are not supported"
417
- when CloudEvents::CloudEventsError
421
+ ::CloudEvents::HttpContentError.new "Batched CloudEvents are not supported"
422
+ when ::CloudEvents::CloudEventsError
418
423
  event
419
424
  else
420
425
  raise "Unexpected event type: #{event.class}"
@@ -427,14 +432,15 @@ module FunctionsFramework
427
432
  def decode_event env
428
433
  @cloud_events.decode_rack_env(env) ||
429
434
  @legacy_events.decode_rack_env(env) ||
430
- raise(CloudEvents::HttpContentError, "Unrecognized event format")
431
- rescue CloudEvents::CloudEventsError => e
435
+ raise(::CloudEvents::HttpContentError, "Unrecognized event format")
436
+ rescue ::CloudEvents::CloudEventsError => e
432
437
  e
433
438
  end
434
439
 
435
440
  def handle_cloud_event event, logger
436
441
  logger.info "FunctionsFramework: Handling CloudEvent"
437
- @function.call event
442
+ calling_context = @function.new_call logger: logger
443
+ calling_context.call event
438
444
  "ok"
439
445
  rescue ::StandardError => e
440
446
  e
@@ -87,7 +87,7 @@ module FunctionsFramework
87
87
  function = ::FunctionsFramework.global_registry[name]
88
88
  case function&.type
89
89
  when :http
90
- Testing.interpret_response { function.call request }
90
+ Testing.interpret_response { function.new_call.call request }
91
91
  when nil
92
92
  raise "Unknown function name #{name}"
93
93
  else
@@ -97,17 +97,17 @@ module FunctionsFramework
97
97
 
98
98
  ##
99
99
  # Call the given event function for testing. The underlying function must
100
- # be of type `:event` or `:cloud_event`.
100
+ # be of type :cloud_event`.
101
101
  #
102
102
  # @param name [String] The name of the function to call
103
- # @param event [FunctionsFramework::CloudEvets::Event] The event to send
103
+ # @param event [::CloudEvents::Event] The event to send
104
104
  # @return [nil]
105
105
  #
106
106
  def call_event name, event
107
107
  function = ::FunctionsFramework.global_registry[name]
108
108
  case function&.type
109
- when :event, :cloud_event
110
- function.call event
109
+ when :cloud_event
110
+ function.new_call.call event
111
111
  nil
112
112
  when nil
113
113
  raise "Unknown function name #{name}"
@@ -116,30 +116,52 @@ module FunctionsFramework
116
116
  end
117
117
  end
118
118
 
119
+ ##
120
+ # Make a Rack request, for passing to a function test.
121
+ #
122
+ # @param url [URI,String] The URL to get, including query params.
123
+ # @param method [String] The HTTP method (defaults to "GET").
124
+ # @param body [String] The HTTP body, if any.
125
+ # @param headers [Array,Hash] HTTP headers. May be given as a hash (of
126
+ # header names mapped to values), an array of strings (where each
127
+ # string is of the form `Header-Name: Header value`), or an array of
128
+ # two-element string arrays.
129
+ # @return [Rack::Request]
130
+ #
131
+ def make_request url, method: ::Rack::GET, body: nil, headers: []
132
+ env = Testing.build_standard_env URI(url), headers
133
+ env[::Rack::REQUEST_METHOD] = method
134
+ env[::Rack::RACK_INPUT] = ::StringIO.new body if body
135
+ ::Rack::Request.new env
136
+ end
137
+
119
138
  ##
120
139
  # Make a simple GET request, for passing to a function test.
121
140
  #
122
141
  # @param url [URI,String] The URL to get.
142
+ # @param headers [Array,Hash] HTTP headers. May be given as a hash (of
143
+ # header names mapped to values), an array of strings (where each
144
+ # string is of the form `Header-Name: Header value`), or an array of
145
+ # two-element string arrays.
123
146
  # @return [Rack::Request]
124
147
  #
125
148
  def make_get_request url, headers = []
126
- env = Testing.build_standard_env URI(url), headers
127
- env[::Rack::REQUEST_METHOD] = ::Rack::GET
128
- ::Rack::Request.new env
149
+ make_request url, headers: headers
129
150
  end
130
151
 
131
152
  ##
132
153
  # Make a simple POST request, for passing to a function test.
133
154
  #
134
155
  # @param url [URI,String] The URL to post to.
135
- # @param data [String] The body to post.
156
+ # @param body [String] The body to post.
157
+ # @param headers [Array,Hash] HTTP headers. May be given as a hash (of
158
+ # header names mapped to values), an array of strings (where each
159
+ # string is of the form `Header-Name: Header value`), or an array of
160
+ # two-element string arrays.
136
161
  # @return [Rack::Request]
137
162
  #
138
- def make_post_request url, data, headers = []
139
- env = Testing.build_standard_env URI(url), headers
140
- env[::Rack::REQUEST_METHOD] = ::Rack::POST
141
- env[::Rack::RACK_INPUT] = ::StringIO.new data
142
- ::Rack::Request.new env
163
+ def make_post_request url, body, headers = []
164
+ make_request url, method: ::Rack::POST, body: body, headers: headers
143
165
  end
144
166
 
145
167
  ##
@@ -152,23 +174,35 @@ module FunctionsFramework
152
174
  # @param source [String,URI] Event source (optional)
153
175
  # @param type [String] Event type (optional)
154
176
  # @param spec_version [String] Spec version (optional)
155
- # @param data_content_type [String,FunctionsFramework::CloudEvents::ContentType]
177
+ # @param data_content_type [String,::CloudEvents::ContentType]
156
178
  # Content type for the data (optional)
157
179
  # @param data_schema [String,URI] Data schema (optional)
158
180
  # @param subject [String] Subject (optional)
159
181
  # @param time [String,DateTime] Event timestamp (optional)
160
- # @return [FunctionsFramework::CloudEvents::Event]
182
+ # @return [::CloudEvents::Event]
161
183
  #
162
184
  def make_cloud_event data,
163
- id: nil, source: nil, type: nil, spec_version: nil,
164
- data_content_type: nil, data_schema: nil, subject: nil, time: nil
185
+ id: nil,
186
+ source: nil,
187
+ type: nil,
188
+ spec_version: nil,
189
+ data_content_type: nil,
190
+ data_schema: nil,
191
+ subject: nil,
192
+ time: nil
165
193
  id ||= "random-id-#{rand 100_000_000}"
166
194
  source ||= "functions-framework-testing"
167
195
  type ||= "com.example.test"
168
196
  spec_version ||= "1.0"
169
- CloudEvents::Event.new id: id, source: source, type: type, spec_version: spec_version,
170
- data_content_type: data_content_type, data_schema: data_schema,
171
- subject: subject, time: time, data: data
197
+ ::CloudEvents::Event.new id: id,
198
+ source: source,
199
+ type: type,
200
+ spec_version: spec_version,
201
+ data_content_type: data_content_type,
202
+ data_schema: data_schema,
203
+ subject: subject,
204
+ time: time,
205
+ data: data
172
206
  end
173
207
 
174
208
  extend self
@@ -245,7 +279,11 @@ module FunctionsFramework
245
279
  ::Rack::RACK_ERRORS => ::StringIO.new
246
280
  }
247
281
  headers.each do |header|
248
- name, value = header.split ":"
282
+ if header.is_a? String
283
+ name, value = header.split ":"
284
+ elsif header.is_a? Array
285
+ name, value = header
286
+ end
249
287
  next unless name && value
250
288
  name = name.strip.upcase.tr "-", "_"
251
289
  name = "HTTP_#{name}" unless ["CONTENT_TYPE", "CONTENT_LENGTH"].include? name
@@ -17,5 +17,5 @@ module FunctionsFramework
17
17
  # Version of the Ruby Functions Framework
18
18
  # @return [String]
19
19
  #
20
- VERSION = "0.3.0".freeze
20
+ VERSION = "0.5.1".freeze
21
21
  end
metadata CHANGED
@@ -1,141 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: functions_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-25 00:00:00.000000000 Z
11
+ date: 2020-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: puma
14
+ name: cloud_events
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.3'
19
+ version: '0.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.3'
26
+ version: '0.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rack
28
+ name: puma
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.1'
33
+ version: '4.3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.1'
41
- - !ruby/object:Gem::Dependency
42
- name: google-style
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 1.24.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 1.24.0
55
- - !ruby/object:Gem::Dependency
56
- name: minitest
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '5.13'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '5.13'
69
- - !ruby/object:Gem::Dependency
70
- name: minitest-focus
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '1.1'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.1'
83
- - !ruby/object:Gem::Dependency
84
- name: minitest-rg
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '5.2'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '5.2'
97
- - !ruby/object:Gem::Dependency
98
- name: redcarpet
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '3.5'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '3.5'
111
- - !ruby/object:Gem::Dependency
112
- name: toys
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: 0.10.0
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: 0.10.0
40
+ version: '4.3'
125
41
  - !ruby/object:Gem::Dependency
126
- name: yard
42
+ name: rack
127
43
  requirement: !ruby/object:Gem::Requirement
128
44
  requirements:
129
45
  - - "~>"
130
46
  - !ruby/object:Gem::Version
131
- version: 0.9.24
132
- type: :development
47
+ version: '2.1'
48
+ type: :runtime
133
49
  prerelease: false
134
50
  version_requirements: !ruby/object:Gem::Requirement
135
51
  requirements:
136
52
  - - "~>"
137
53
  - !ruby/object:Gem::Version
138
- version: 0.9.24
54
+ version: '2.1'
139
55
  description: The Functions Framework implementation for Ruby.
140
56
  email:
141
57
  - dazuma@google.com
@@ -158,13 +74,6 @@ files:
158
74
  - docs/writing-functions.md
159
75
  - lib/functions_framework.rb
160
76
  - lib/functions_framework/cli.rb
161
- - lib/functions_framework/cloud_events.rb
162
- - lib/functions_framework/cloud_events/content_type.rb
163
- - lib/functions_framework/cloud_events/errors.rb
164
- - lib/functions_framework/cloud_events/event.rb
165
- - lib/functions_framework/cloud_events/event/v1.rb
166
- - lib/functions_framework/cloud_events/http_binding.rb
167
- - lib/functions_framework/cloud_events/json_format.rb
168
77
  - lib/functions_framework/function.rb
169
78
  - lib/functions_framework/legacy_event_converter.rb
170
79
  - lib/functions_framework/registry.rb
@@ -174,7 +83,11 @@ files:
174
83
  homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
175
84
  licenses:
176
85
  - Apache-2.0
177
- metadata: {}
86
+ metadata:
87
+ changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.5.1/file.CHANGELOG.html
88
+ source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
89
+ bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
90
+ documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.5.1
178
91
  post_install_message:
179
92
  rdoc_options: []
180
93
  require_paths: