functions_framework 0.3.0 → 0.5.1
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 +4 -4
- data/CHANGELOG.md +33 -1
- data/README.md +8 -8
- data/docs/deploying-functions.md +7 -1
- data/docs/overview.md +3 -3
- data/docs/testing-functions.md +9 -11
- data/docs/writing-functions.md +33 -19
- data/lib/functions_framework.rb +10 -17
- data/lib/functions_framework/function.rb +81 -24
- data/lib/functions_framework/legacy_event_converter.rb +29 -30
- data/lib/functions_framework/registry.rb +2 -16
- data/lib/functions_framework/server.rb +20 -14
- data/lib/functions_framework/testing.rb +60 -22
- data/lib/functions_framework/version.rb +1 -1
- metadata +17 -104
- data/lib/functions_framework/cloud_events.rb +0 -43
- data/lib/functions_framework/cloud_events/content_type.rb +0 -139
- data/lib/functions_framework/cloud_events/errors.rb +0 -42
- data/lib/functions_framework/cloud_events/event.rb +0 -79
- data/lib/functions_framework/cloud_events/event/v1.rb +0 -363
- data/lib/functions_framework/cloud_events/http_binding.rb +0 -270
- data/lib/functions_framework/cloud_events/json_format.rb +0 -122
@@ -1,270 +0,0 @@
|
|
1
|
-
# Copyright 2020 Google LLC
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
require "functions_framework/cloud_events/json_format"
|
16
|
-
|
17
|
-
module FunctionsFramework
|
18
|
-
module CloudEvents
|
19
|
-
##
|
20
|
-
# HTTP binding for CloudEvents.
|
21
|
-
#
|
22
|
-
# This class implements HTTP binding, including unmarshalling of events from
|
23
|
-
# Rack environment data, and marshalling of events to Rack environment data.
|
24
|
-
# It supports binary (i.e. header-based) HTTP content, as well as structured
|
25
|
-
# (body-based) content that can delegate to formatters such as JSON.
|
26
|
-
#
|
27
|
-
# See https://github.com/cloudevents/spec/blob/master/http-protocol-binding.md
|
28
|
-
#
|
29
|
-
class HttpBinding
|
30
|
-
##
|
31
|
-
# Returns a default binding, with JSON supported.
|
32
|
-
#
|
33
|
-
def self.default
|
34
|
-
@default ||= begin
|
35
|
-
http_binding = new
|
36
|
-
json_format = JsonFormat.new
|
37
|
-
http_binding.register_structured_formatter "json", json_format
|
38
|
-
http_binding.register_batched_formatter "json", json_format
|
39
|
-
http_binding
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
##
|
44
|
-
# Create an empty HTTP binding.
|
45
|
-
#
|
46
|
-
def initialize
|
47
|
-
@structured_formatters = {}
|
48
|
-
@batched_formatters = {}
|
49
|
-
end
|
50
|
-
|
51
|
-
##
|
52
|
-
# Register a formatter for the given type.
|
53
|
-
#
|
54
|
-
# A formatter must respond to the methods `#encode` and `#decode`. See
|
55
|
-
# {FunctionsFramework::CloudEvents::JsonFormat} for an example.
|
56
|
-
#
|
57
|
-
# @param type [String] The subtype format that should be handled by
|
58
|
-
# this formatter.
|
59
|
-
# @param formatter [Object] The formatter object.
|
60
|
-
# @return [self]
|
61
|
-
#
|
62
|
-
def register_structured_formatter type, formatter
|
63
|
-
formatters = @structured_formatters[type.to_s.strip.downcase] ||= []
|
64
|
-
formatters << formatter unless formatters.include? formatter
|
65
|
-
self
|
66
|
-
end
|
67
|
-
|
68
|
-
##
|
69
|
-
# Register a batch formatter for the given type.
|
70
|
-
#
|
71
|
-
# A batch formatter must respond to the methods `#encode_batch` and
|
72
|
-
# `#decode_batch`. See {FunctionsFramework::CloudEvents::JsonFormat} for
|
73
|
-
# an example.
|
74
|
-
#
|
75
|
-
# @param type [String] The subtype format that should be handled by
|
76
|
-
# this formatter.
|
77
|
-
# @param formatter [Object] The formatter object.
|
78
|
-
# @return [self]
|
79
|
-
#
|
80
|
-
def register_batched_formatter type, formatter
|
81
|
-
formatters = @batched_formatters[type.to_s.strip.downcase] ||= []
|
82
|
-
formatters << formatter unless formatters.include? formatter
|
83
|
-
self
|
84
|
-
end
|
85
|
-
|
86
|
-
##
|
87
|
-
# Decode an event from the given Rack environment hash. Following the
|
88
|
-
# CloudEvents spec, this chooses a handler based on the Content-Type of
|
89
|
-
# the request.
|
90
|
-
#
|
91
|
-
# @param env [Hash] The Rack environment.
|
92
|
-
# @param format_args [keywords] Extra args to pass to the formatter.
|
93
|
-
# @return [FunctionsFramework::CloudEvents::Event] if the request
|
94
|
-
# includes a single structured or binary event.
|
95
|
-
# @return [Array<FunctionsFramework::CloudEvents::Event>] if the request
|
96
|
-
# includes a batch of structured events.
|
97
|
-
# @return [nil] if the request was not recognized as a CloudEvent.
|
98
|
-
#
|
99
|
-
def decode_rack_env env, **format_args
|
100
|
-
content_type_header = env["CONTENT_TYPE"]
|
101
|
-
content_type = ContentType.new content_type_header if content_type_header
|
102
|
-
input = env["rack.input"]
|
103
|
-
if input && content_type&.media_type == "application"
|
104
|
-
case content_type.subtype_prefix
|
105
|
-
when "cloudevents"
|
106
|
-
input.set_encoding content_type.charset if content_type.charset
|
107
|
-
return decode_structured_content input.read, content_type.subtype_format, **format_args
|
108
|
-
when "cloudevents-batch"
|
109
|
-
input.set_encoding content_type.charset if content_type.charset
|
110
|
-
return decode_batched_content input.read, content_type.subtype_format, **format_args
|
111
|
-
end
|
112
|
-
end
|
113
|
-
decode_binary_content env, content_type
|
114
|
-
end
|
115
|
-
|
116
|
-
##
|
117
|
-
# Decode a single event from the given content data. This should be
|
118
|
-
# passed the request body, if the Content-Type is of the form
|
119
|
-
# `application/cloudevents+format`.
|
120
|
-
#
|
121
|
-
# @param input [String] The string content.
|
122
|
-
# @param format [String] The format code (e.g. "json").
|
123
|
-
# @param format_args [keywords] Extra args to pass to the formatter.
|
124
|
-
# @return [FunctionsFramework::CloudEvents::Event]
|
125
|
-
#
|
126
|
-
def decode_structured_content input, format, **format_args
|
127
|
-
handlers = @structured_formatters[format] || []
|
128
|
-
handlers.reverse_each do |handler|
|
129
|
-
event = handler.decode input, **format_args
|
130
|
-
return event if event
|
131
|
-
end
|
132
|
-
raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
|
133
|
-
end
|
134
|
-
|
135
|
-
##
|
136
|
-
# Decode a batch of events from the given content data. This should be
|
137
|
-
# passed the request body, if the Content-Type is of the form
|
138
|
-
# `application/cloudevents-batch+format`.
|
139
|
-
#
|
140
|
-
# @param input [String] The string content.
|
141
|
-
# @param format [String] The format code (e.g. "json").
|
142
|
-
# @param format_args [keywords] Extra args to pass to the formatter.
|
143
|
-
# @return [Array<FunctionsFramework::CloudEvents::Event>]
|
144
|
-
#
|
145
|
-
def decode_batched_content input, format, **format_args
|
146
|
-
handlers = @batched_formatters[format] || []
|
147
|
-
handlers.reverse_each do |handler|
|
148
|
-
events = handler.decode_batch input, **format_args
|
149
|
-
return events if events
|
150
|
-
end
|
151
|
-
raise HttpContentError, "Unknown cloudevents batch format: #{format.inspect}"
|
152
|
-
end
|
153
|
-
|
154
|
-
##
|
155
|
-
# Decode an event from the given Rack environment in binary content mode.
|
156
|
-
#
|
157
|
-
# @param env [Hash] Rack environment hash.
|
158
|
-
# @param content_type [FunctionsFramework::CloudEvents::ContentType]
|
159
|
-
# the content type from the Rack environment.
|
160
|
-
# @return [FunctionsFramework::CloudEvents::Event] if a CloudEvent
|
161
|
-
# could be decoded from the Rack environment.
|
162
|
-
# @return [nil] if the Rack environment does not indicate a CloudEvent
|
163
|
-
#
|
164
|
-
def decode_binary_content env, content_type
|
165
|
-
spec_version = env["HTTP_CE_SPECVERSION"]
|
166
|
-
return nil if spec_version.nil?
|
167
|
-
raise SpecVersionError, "Unrecognized specversion: #{spec_version}" unless spec_version == "1.0"
|
168
|
-
input = env["rack.input"]
|
169
|
-
data = if input
|
170
|
-
input.set_encoding content_type.charset if content_type&.charset
|
171
|
-
input.read
|
172
|
-
end
|
173
|
-
attributes = { "spec_version" => spec_version, "data" => data }
|
174
|
-
attributes["data_content_type"] = content_type if content_type
|
175
|
-
omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"]
|
176
|
-
env.each do |key, value|
|
177
|
-
match = /^HTTP_CE_(\w+)$/.match key
|
178
|
-
next unless match
|
179
|
-
attr_name = match[1].downcase
|
180
|
-
attributes[attr_name] = value unless omit_names.include? attr_name
|
181
|
-
end
|
182
|
-
Event.create spec_version: spec_version, attributes: attributes
|
183
|
-
end
|
184
|
-
|
185
|
-
##
|
186
|
-
# Encode a single event to content data in the given format.
|
187
|
-
#
|
188
|
-
# The result is a two-element array where the first element is a headers
|
189
|
-
# list (as defined in the Rack specification) and the second is a string
|
190
|
-
# containing the HTTP body content. The headers list will contain only
|
191
|
-
# one header, a `Content-Type` whose value is of the form
|
192
|
-
# `application/cloudevents+format`.
|
193
|
-
#
|
194
|
-
# @param event [FunctionsFramework::CloudEvents::Event] The event.
|
195
|
-
# @param format [String] The format code (e.g. "json")
|
196
|
-
# @param format_args [keywords] Extra args to pass to the formatter.
|
197
|
-
# @return [Array(headers,String)]
|
198
|
-
#
|
199
|
-
def encode_structured_content event, format, **format_args
|
200
|
-
handlers = @structured_formatters[format] || []
|
201
|
-
handlers.reverse_each do |handler|
|
202
|
-
content = handler.encode event, **format_args
|
203
|
-
return [{ "Content-Type" => "application/cloudevents+#{format}" }, content] if content
|
204
|
-
end
|
205
|
-
raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
|
206
|
-
end
|
207
|
-
|
208
|
-
##
|
209
|
-
# Encode a batch of events to content data in the given format.
|
210
|
-
#
|
211
|
-
# The result is a two-element array where the first element is a headers
|
212
|
-
# list (as defined in the Rack specification) and the second is a string
|
213
|
-
# containing the HTTP body content. The headers list will contain only
|
214
|
-
# one header, a `Content-Type` whose value is of the form
|
215
|
-
# `application/cloudevents-batch+format`.
|
216
|
-
#
|
217
|
-
# @param events [Array<FunctionsFramework::CloudEvents::Event>] The batch
|
218
|
-
# of events.
|
219
|
-
# @param format [String] The format code (e.g. "json").
|
220
|
-
# @param format_args [keywords] Extra args to pass to the formatter.
|
221
|
-
# @return [Array(headers,String)]
|
222
|
-
#
|
223
|
-
def encode_batched_content events, format, **format_args
|
224
|
-
handlers = @batched_formatters[format] || []
|
225
|
-
handlers.reverse_each do |handler|
|
226
|
-
content = handler.encode_batch events, **format_args
|
227
|
-
return [{ "Content-Type" => "application/cloudevents-batch+#{format}" }, content] if content
|
228
|
-
end
|
229
|
-
raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
|
230
|
-
end
|
231
|
-
|
232
|
-
##
|
233
|
-
# Encode an event to content and headers, in binary content mode.
|
234
|
-
#
|
235
|
-
# The result is a two-element array where the first element is a headers
|
236
|
-
# list (as defined in the Rack specification) and the second is a string
|
237
|
-
# containing the HTTP body content.
|
238
|
-
#
|
239
|
-
# @param event [FunctionsFramework::CloudEvents::Event] The event.
|
240
|
-
# @return [Array(headers,String)]
|
241
|
-
#
|
242
|
-
def encode_binary_content event
|
243
|
-
headers = {}
|
244
|
-
body = nil
|
245
|
-
event.to_h.each do |key, value|
|
246
|
-
if key == "data"
|
247
|
-
body = value
|
248
|
-
elsif key == "datacontenttype"
|
249
|
-
headers["Content-Type"] = value
|
250
|
-
else
|
251
|
-
headers["CE-#{key}"] = value
|
252
|
-
end
|
253
|
-
end
|
254
|
-
if body.is_a? ::String
|
255
|
-
headers["Content-Type"] ||= if body.encoding == ::Encoding.ASCII_8BIT
|
256
|
-
"application/octet-stream"
|
257
|
-
else
|
258
|
-
"text/plain; charset=#{body.encoding.name.downcase}"
|
259
|
-
end
|
260
|
-
elsif body.nil?
|
261
|
-
headers.delete "Content-Type"
|
262
|
-
else
|
263
|
-
body = ::JSON.dump body
|
264
|
-
headers["Content-Type"] ||= "application/json; charset=#{body.encoding.name.downcase}"
|
265
|
-
end
|
266
|
-
[headers, body]
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
@@ -1,122 +0,0 @@
|
|
1
|
-
# Copyright 2020 Google LLC
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
require "base64"
|
16
|
-
require "json"
|
17
|
-
|
18
|
-
module FunctionsFramework
|
19
|
-
module CloudEvents
|
20
|
-
##
|
21
|
-
# An implementation of JSON format and JSON batch format.
|
22
|
-
#
|
23
|
-
# See https://github.com/cloudevents/spec/blob/master/json-format.md
|
24
|
-
#
|
25
|
-
class JsonFormat
|
26
|
-
##
|
27
|
-
# Decode an event from the given input JSON string.
|
28
|
-
#
|
29
|
-
# @param json [String] A JSON-formatted string
|
30
|
-
# @return [FunctionsFramework::CloudEvents::Event]
|
31
|
-
#
|
32
|
-
def decode json, **_other_kwargs
|
33
|
-
structure = ::JSON.parse json
|
34
|
-
decode_hash_structure structure
|
35
|
-
end
|
36
|
-
|
37
|
-
##
|
38
|
-
# Encode an event to a JSON string.
|
39
|
-
#
|
40
|
-
# @param event [FunctionsFramework::CloudEvents::Event] An input event.
|
41
|
-
# @param sort [boolean] Whether to sort keys of the JSON output.
|
42
|
-
# @return [String] The JSON representation.
|
43
|
-
#
|
44
|
-
def encode event, sort: false, **_other_kwargs
|
45
|
-
structure = encode_hash_structure event
|
46
|
-
structure = sort_keys structure if sort
|
47
|
-
::JSON.dump structure
|
48
|
-
end
|
49
|
-
|
50
|
-
##
|
51
|
-
# Decode a batch of events from the given input string.
|
52
|
-
#
|
53
|
-
# @param json [String] A JSON-formatted string
|
54
|
-
# @return [Array<FunctionsFramework::CloudEvents::Event>]
|
55
|
-
#
|
56
|
-
def decode_batch json, **_other_kwargs
|
57
|
-
structure_array = Array(::JSON.parse(json))
|
58
|
-
structure_array.map do |structure|
|
59
|
-
decode_hash_structure structure
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
##
|
64
|
-
# Encode a batch of event to a JSON string.
|
65
|
-
#
|
66
|
-
# @param events [Array<FunctionsFramework::CloudEvents::Event>] An array
|
67
|
-
# of input events.
|
68
|
-
# @param sort [boolean] Whether to sort keys of the JSON output.
|
69
|
-
# @return [String] The JSON representation.
|
70
|
-
#
|
71
|
-
def encode_batch events, sort: false, **_other_kwargs
|
72
|
-
structure_array = Array(events).map do |event|
|
73
|
-
structure = encode_hash_structure event
|
74
|
-
sort ? sort_keys(structure) : structure
|
75
|
-
end
|
76
|
-
::JSON.dump structure_array
|
77
|
-
end
|
78
|
-
|
79
|
-
##
|
80
|
-
# Decode a single event from a hash data structure with keys and types
|
81
|
-
# conforming to the JSON event format.
|
82
|
-
#
|
83
|
-
# @param structure [Hash] An input hash.
|
84
|
-
# @return [FunctionsFramework::CloudEvents::Event]
|
85
|
-
#
|
86
|
-
def decode_hash_structure structure
|
87
|
-
if structure.key? "data_base64"
|
88
|
-
structure = structure.dup
|
89
|
-
structure["data"] = ::Base64.decode64 structure.delete "data_base64"
|
90
|
-
end
|
91
|
-
Event.create spec_version: structure["specversion"], attributes: structure
|
92
|
-
end
|
93
|
-
|
94
|
-
##
|
95
|
-
# Encode a single event to a hash data structure with keys and types
|
96
|
-
# conforming to the JSON event format.
|
97
|
-
#
|
98
|
-
# @param event [FunctionsFramework::CloudEvents::Event] An input event.
|
99
|
-
# @return [String] The hash structure.
|
100
|
-
#
|
101
|
-
def encode_hash_structure event
|
102
|
-
structure = event.to_h
|
103
|
-
data = structure["data"]
|
104
|
-
if data.is_a?(::String) && data.encoding == ::Encoding::ASCII_8BIT
|
105
|
-
structure.delete "data"
|
106
|
-
structure["data_base64"] = ::Base64.encode64 data
|
107
|
-
end
|
108
|
-
structure
|
109
|
-
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
def sort_keys hash
|
114
|
-
result = {}
|
115
|
-
hash.keys.sort.each do |key|
|
116
|
-
result[key] = hash[key]
|
117
|
-
end
|
118
|
-
result
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|