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.
@@ -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