functions_framework 0.1.1 → 0.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 +4 -4
- data/.yardopts +6 -2
- data/CHANGELOG.md +44 -0
- data/README.md +56 -136
- data/bin/functions-framework-ruby +19 -0
- data/docs/deploying-functions.md +182 -0
- data/docs/overview.md +142 -0
- data/docs/running-a-functions-server.md +122 -0
- data/docs/testing-functions.md +169 -0
- data/docs/writing-functions.md +275 -0
- data/lib/functions_framework.rb +16 -50
- data/lib/functions_framework/cli.rb +71 -13
- data/lib/functions_framework/cloud_events.rb +12 -110
- data/lib/functions_framework/cloud_events/content_type.rb +107 -30
- data/lib/functions_framework/cloud_events/errors.rb +42 -0
- data/lib/functions_framework/cloud_events/event.rb +56 -249
- data/lib/functions_framework/cloud_events/event/field_interpreter.rb +150 -0
- data/lib/functions_framework/cloud_events/event/v0.rb +236 -0
- data/lib/functions_framework/cloud_events/event/v1.rb +223 -0
- data/lib/functions_framework/cloud_events/http_binding.rb +310 -0
- data/lib/functions_framework/cloud_events/json_format.rb +173 -0
- data/lib/functions_framework/function.rb +80 -26
- data/lib/functions_framework/legacy_event_converter.rb +145 -0
- data/lib/functions_framework/registry.rb +0 -39
- data/lib/functions_framework/server.rb +61 -51
- data/lib/functions_framework/testing.rb +64 -24
- data/lib/functions_framework/version.rb +1 -1
- metadata +16 -4
- data/lib/functions_framework/cloud_events/binary_content.rb +0 -59
- data/lib/functions_framework/cloud_events/json_structure.rb +0 -88
@@ -0,0 +1,42 @@
|
|
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
|
+
module FunctionsFramework
|
16
|
+
module CloudEvents
|
17
|
+
##
|
18
|
+
# Base class for all CloudEvents errors.
|
19
|
+
#
|
20
|
+
class CloudEventsError < ::StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Errors indicating unsupported or incorrectly formatted HTTP content or
|
25
|
+
# headers.
|
26
|
+
#
|
27
|
+
class HttpContentError < CloudEventsError
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Errors indicating an unsupported or incorrect spec version.
|
32
|
+
#
|
33
|
+
class SpecVersionError < CloudEventsError
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Errors related to CloudEvent attributes.
|
38
|
+
#
|
39
|
+
class AttributeError < CloudEventsError
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -15,262 +15,69 @@
|
|
15
15
|
require "date"
|
16
16
|
require "uri"
|
17
17
|
|
18
|
+
require "functions_framework/cloud_events/event/field_interpreter"
|
19
|
+
require "functions_framework/cloud_events/event/v0"
|
20
|
+
require "functions_framework/cloud_events/event/v1"
|
21
|
+
|
18
22
|
module FunctionsFramework
|
19
23
|
module CloudEvents
|
20
24
|
##
|
21
|
-
#
|
25
|
+
# An Event object represents a complete CloudEvent, including both data and
|
26
|
+
# context attributes. The following are true of all event objects:
|
22
27
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
28
|
+
# * Event classes are defined within this module. For example, events
|
29
|
+
# conforming to the CloudEvents 1.0 specification are of type
|
30
|
+
# {FunctionsFramework::CloudEvents::Event::V1}.
|
31
|
+
# * All event classes include this module, so you can use
|
32
|
+
# `is_a? FunctionsFramework::CloudEvents::Event` to test whether an
|
33
|
+
# object is an event.
|
34
|
+
# * All event objects are immutable. Data and atribute values can be
|
35
|
+
# retrieved but not modified. To "modify" an event, make a copy with
|
36
|
+
# the desired changes. Generally, event classes will provide a helper
|
37
|
+
# method for this purpose.
|
38
|
+
# * All event objects have a `spec_version` method that returns the
|
39
|
+
# version of the CloudEvents spec implemented by that event. (Other
|
40
|
+
# methods may be different, depending on the spec version.)
|
27
41
|
#
|
28
|
-
#
|
29
|
-
# descriptions of the various attributes.
|
42
|
+
# To create an event, you may either:
|
30
43
|
#
|
31
|
-
class
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
@time, @time_string = interpret_date_time "time", time
|
67
|
-
end
|
68
|
-
|
69
|
-
##
|
70
|
-
# Create and return a copy of this event with the given changes. See the
|
71
|
-
# constructor for the parameters that can be passed.
|
72
|
-
#
|
73
|
-
# @param changes [keywords] See {#initialize} for a list of arguments.
|
74
|
-
# @return [FunctionFramework::CloudEvents::Event]
|
75
|
-
#
|
76
|
-
def with **changes
|
77
|
-
params = {
|
78
|
-
id: id,
|
79
|
-
source: source,
|
80
|
-
type: type,
|
81
|
-
spec_version: spec_version,
|
82
|
-
data: data,
|
83
|
-
data_content_type: data_content_type,
|
84
|
-
data_schema: data_schema,
|
85
|
-
subject: subject,
|
86
|
-
time: time
|
87
|
-
}
|
88
|
-
params.merge! changes
|
89
|
-
Event.new(**params)
|
90
|
-
end
|
91
|
-
|
92
|
-
##
|
93
|
-
# The `id` field
|
94
|
-
# @return [String]
|
95
|
-
#
|
96
|
-
attr_reader :id
|
97
|
-
|
98
|
-
##
|
99
|
-
# The `source` field as a `URI` object
|
100
|
-
# @return [URI]
|
101
|
-
#
|
102
|
-
attr_reader :source
|
103
|
-
|
104
|
-
##
|
105
|
-
# The string representation of the `source` field
|
106
|
-
# @return [String]
|
107
|
-
#
|
108
|
-
attr_reader :source_string
|
109
|
-
|
110
|
-
##
|
111
|
-
# The `type` field
|
112
|
-
# @return [String]
|
113
|
-
#
|
114
|
-
attr_reader :type
|
115
|
-
|
116
|
-
##
|
117
|
-
# The `specversion` field
|
118
|
-
# @return [String]
|
119
|
-
#
|
120
|
-
attr_reader :spec_version
|
121
|
-
alias specversion spec_version
|
122
|
-
|
123
|
-
##
|
124
|
-
# The event-specific data, or `nil` if there is no data.
|
125
|
-
#
|
126
|
-
# Data may be one of the following types:
|
127
|
-
# * Binary data, represented by a `String` using `ASCII-8BIT` encoding
|
128
|
-
# * A string in some other encoding such as `UTF-8` or `US-ASCII`
|
129
|
-
# * Any JSON data type, such as String, boolean, Integer, Array, or Hash
|
130
|
-
#
|
131
|
-
# @return [Object]
|
132
|
-
#
|
133
|
-
attr_reader :data
|
134
|
-
|
135
|
-
##
|
136
|
-
# The optional `datacontenttype` field as a
|
137
|
-
# {FunctionsFramework::CloudEvents::ContentType} object, or `nil` if the
|
138
|
-
# field is absent
|
139
|
-
#
|
140
|
-
# @return [FunctionsFramework::CloudEvents::ContentType,nil]
|
141
|
-
#
|
142
|
-
attr_reader :data_content_type
|
143
|
-
alias datacontenttype data_content_type
|
144
|
-
|
145
|
-
##
|
146
|
-
# The string representation of the optional `datacontenttype` field, or
|
147
|
-
# `nil` if the field is absent
|
148
|
-
#
|
149
|
-
# @return [String,nil]
|
150
|
-
#
|
151
|
-
attr_reader :data_content_type_string
|
152
|
-
alias datacontenttype_string data_content_type_string
|
153
|
-
|
154
|
-
##
|
155
|
-
# The optional `dataschema` field as a `URI` object, or `nil` if the
|
156
|
-
# field is absent
|
157
|
-
#
|
158
|
-
# @return [URI,nil]
|
159
|
-
#
|
160
|
-
attr_reader :data_schema
|
161
|
-
alias dataschema data_schema
|
162
|
-
|
163
|
-
##
|
164
|
-
# The string representation of the optional `dataschema` field, or `nil`
|
165
|
-
# if the field is absent
|
166
|
-
#
|
167
|
-
# @return [String,nil]
|
168
|
-
#
|
169
|
-
attr_reader :data_schema_string
|
170
|
-
alias dataschema_string data_schema_string
|
171
|
-
|
172
|
-
##
|
173
|
-
# The optional `subject` field, or `nil` if the field is absent
|
174
|
-
#
|
175
|
-
# @return [String,nil]
|
176
|
-
#
|
177
|
-
attr_reader :subject
|
178
|
-
|
179
|
-
##
|
180
|
-
# The optional `time` field as a `DateTime` object, or `nil` if the field
|
181
|
-
# is absent
|
182
|
-
#
|
183
|
-
# @return [DateTime,nil]
|
184
|
-
#
|
185
|
-
attr_reader :time
|
186
|
-
|
187
|
-
##
|
188
|
-
# The string representation of the optional `time` field, or `nil` if the
|
189
|
-
# field is absent
|
190
|
-
#
|
191
|
-
# @return [String,nil]
|
192
|
-
#
|
193
|
-
attr_reader :time_string
|
194
|
-
|
195
|
-
## @private
|
196
|
-
def == other
|
197
|
-
other.is_a?(ContentType) &&
|
198
|
-
id == other.id &&
|
199
|
-
source == other.source &&
|
200
|
-
type == other.type &&
|
201
|
-
spec_version == other.spec_version &&
|
202
|
-
data_content_type == other.data_content_type &&
|
203
|
-
data_schema == other.data_schema &&
|
204
|
-
subject == other.subject &&
|
205
|
-
time == other.time &&
|
206
|
-
data == other.data
|
207
|
-
end
|
208
|
-
alias eql? ==
|
209
|
-
|
210
|
-
## @private
|
211
|
-
def hash
|
212
|
-
@hash ||=
|
213
|
-
[id, source, type, spec_version, data_content_type, data_schema, subject, time, data].hash
|
214
|
-
end
|
215
|
-
|
216
|
-
private
|
217
|
-
|
218
|
-
def interpret_string name, input, required = false
|
219
|
-
case input
|
220
|
-
when ::String
|
221
|
-
raise ::ArgumentError, "The #{name} field cannot be empty" if input.empty?
|
222
|
-
input
|
223
|
-
when nil
|
224
|
-
raise ::ArgumentError, "The #{name} field is required" if required
|
225
|
-
nil
|
226
|
-
else
|
227
|
-
raise ::ArgumentError, "Illegal type for #{name} field: #{input.inspect}"
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def interpret_uri name, input, required = false
|
232
|
-
case input
|
233
|
-
when ::String
|
234
|
-
raise ::ArgumentError, "The #{name} field cannot be empty" if input.empty?
|
235
|
-
[::URI.parse(input), input]
|
236
|
-
when ::URI::Generic
|
237
|
-
[input, input.to_s]
|
238
|
-
when nil
|
239
|
-
raise ::ArgumentError, "The #{name} field is required" if required
|
240
|
-
[nil, nil]
|
241
|
-
else
|
242
|
-
raise ::ArgumentError, "Illegal type for #{name} field: #{input.inspect}"
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def interpret_date_time name, input, required = false
|
247
|
-
case input
|
248
|
-
when ::String
|
249
|
-
raise ::ArgumentError, "The #{name} field cannot be empty" if input.empty?
|
250
|
-
[::DateTime.rfc3339(input), input]
|
251
|
-
when ::DateTime
|
252
|
-
[input, input.rfc3339]
|
253
|
-
when nil
|
254
|
-
raise ::ArgumentError, "The #{name} field is required" if required
|
255
|
-
[nil, nil]
|
256
|
-
else
|
257
|
-
raise ::ArgumentError, "Illegal type for #{name} field: #{input.inspect}"
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def interpret_content_type name, input, required = false
|
262
|
-
case input
|
263
|
-
when ::String
|
264
|
-
raise ::ArgumentError, "The #{name} field cannot be empty" if input.empty?
|
265
|
-
[ContentType.new(input), input]
|
266
|
-
when ContentType
|
267
|
-
[input, input.to_s]
|
268
|
-
when nil
|
269
|
-
raise ::ArgumentError, "The #{name} field is required" if required
|
270
|
-
[nil, nil]
|
271
|
-
else
|
272
|
-
raise ::ArgumentError, "Illegal type for #{name} field: #{input.inspect}"
|
44
|
+
# * Construct an instance of the event class directly, for example by
|
45
|
+
# calling {Event::V1.new} and passing a set of attributes.
|
46
|
+
# * Call {Event.create} and pass a spec version and a set of attributes.
|
47
|
+
# This will choose the appropriate event class based on the version.
|
48
|
+
# * Decode an event from another representation. For example, use
|
49
|
+
# {CloudEvents::JsonFormat} to decode an event from JSON, or use
|
50
|
+
# {CloudEvents::HttpBinding} to decode an event from an HTTP request.
|
51
|
+
#
|
52
|
+
# See https://github.com/cloudevents/spec for more information about
|
53
|
+
# CloudEvents. The documentation for the individual event classes
|
54
|
+
# {FunctionsFramework::CloudEvents::Event::V0} and
|
55
|
+
# {FunctionsFramework::CloudEvents::Event::V1} also include links to their
|
56
|
+
# respective specifications.
|
57
|
+
#
|
58
|
+
module Event
|
59
|
+
class << self
|
60
|
+
##
|
61
|
+
# Create a new cloud event object with the given version. Generally,
|
62
|
+
# you must also pass additional keyword arguments providing the event's
|
63
|
+
# data and attributes. For example, if you pass `1.0` as the
|
64
|
+
# `spec_version`, the remaining keyword arguments will be passed
|
65
|
+
# through to the {Event::V1.new} constructor.
|
66
|
+
#
|
67
|
+
# @param spec_version [String] The required `specversion` field.
|
68
|
+
# @param kwargs [keywords] Additional parameters for the event.
|
69
|
+
#
|
70
|
+
def create spec_version:, **kwargs
|
71
|
+
case spec_version
|
72
|
+
when "0.3"
|
73
|
+
V0.new spec_version: spec_version, **kwargs
|
74
|
+
when /^1(\.|$)/
|
75
|
+
V1.new spec_version: spec_version, **kwargs
|
76
|
+
else
|
77
|
+
raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
|
78
|
+
end
|
273
79
|
end
|
80
|
+
alias new create
|
274
81
|
end
|
275
82
|
end
|
276
83
|
end
|
@@ -0,0 +1,150 @@
|
|
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
|
+
module FunctionsFramework
|
16
|
+
module CloudEvents
|
17
|
+
module Event
|
18
|
+
##
|
19
|
+
# A helper that extracts and interprets event fields from an input hash.
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
#
|
23
|
+
class FieldInterpreter
|
24
|
+
def initialize args
|
25
|
+
@args = keys_to_strings args
|
26
|
+
@attributes = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def finish_attributes
|
30
|
+
@attributes.merge! @args
|
31
|
+
@args = {}
|
32
|
+
@attributes
|
33
|
+
end
|
34
|
+
|
35
|
+
def string keys, required: false
|
36
|
+
object keys, required: required do |value|
|
37
|
+
case value
|
38
|
+
when ::String
|
39
|
+
raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
|
40
|
+
[value, value]
|
41
|
+
else
|
42
|
+
raise AttributeError, "Illegal type for #{keys.first}:" \
|
43
|
+
" String expected but #{value.class} found"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def uri keys, required: false
|
49
|
+
object keys, required: required do |value|
|
50
|
+
case value
|
51
|
+
when ::String
|
52
|
+
raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
|
53
|
+
begin
|
54
|
+
[::URI.parse(value), value]
|
55
|
+
rescue ::URI::InvalidURIError => e
|
56
|
+
raise AttributeError, "Illegal format for #{keys.first}: #{e.message}"
|
57
|
+
end
|
58
|
+
when ::URI::Generic
|
59
|
+
[value, value.to_s]
|
60
|
+
else
|
61
|
+
raise AttributeError, "Illegal type for #{keys.first}:" \
|
62
|
+
" String or URI expected but #{value.class} found"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def rfc3339_date_time keys, required: false
|
68
|
+
object keys, required: required do |value|
|
69
|
+
case value
|
70
|
+
when ::String
|
71
|
+
begin
|
72
|
+
[::DateTime.rfc3339(value), value]
|
73
|
+
rescue ::Date::Error => e
|
74
|
+
raise AttributeError, "Illegal format for #{keys.first}: #{e.message}"
|
75
|
+
end
|
76
|
+
when ::DateTime
|
77
|
+
[value, value.rfc3339]
|
78
|
+
when ::Time
|
79
|
+
value = value.to_datetime
|
80
|
+
[value, value.rfc3339]
|
81
|
+
else
|
82
|
+
raise AttributeError, "Illegal type for #{keys.first}:" \
|
83
|
+
" String, Time, or DateTime expected but #{value.class} found"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def content_type keys, required: false
|
89
|
+
object keys, required: required do |value|
|
90
|
+
case value
|
91
|
+
when ::String
|
92
|
+
raise AttributeError, "The #{keys.first} field cannot be empty" if value.empty?
|
93
|
+
[ContentType.new(value), value]
|
94
|
+
when ContentType
|
95
|
+
[value, value.to_s]
|
96
|
+
else
|
97
|
+
raise AttributeError, "Illegal type for #{keys.first}:" \
|
98
|
+
" String, or ContentType expected but #{value.class} found"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def spec_version keys, accept:
|
104
|
+
object keys, required: true do |value|
|
105
|
+
case value
|
106
|
+
when ::String
|
107
|
+
raise SpecVersionError, "Unrecognized specversion: #{value}" unless accept =~ value
|
108
|
+
[value, value]
|
109
|
+
else
|
110
|
+
raise AttributeError, "Illegal type for #{keys.first}:" \
|
111
|
+
" String expected but #{value.class} found"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
UNDEFINED = Object.new
|
117
|
+
|
118
|
+
def object keys, required: false, allow_nil: false
|
119
|
+
value = UNDEFINED
|
120
|
+
keys.each do |key|
|
121
|
+
key_present = @args.key? key
|
122
|
+
val = @args.delete key
|
123
|
+
value = val if allow_nil && key_present || !allow_nil && !val.nil?
|
124
|
+
end
|
125
|
+
if value == UNDEFINED
|
126
|
+
raise AttributeError, "The #{keys.first} field is required" if required
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
if block_given?
|
130
|
+
converted, raw = yield value
|
131
|
+
else
|
132
|
+
converted = raw = value
|
133
|
+
end
|
134
|
+
@attributes[keys.first] = raw
|
135
|
+
converted
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def keys_to_strings hash
|
141
|
+
result = {}
|
142
|
+
hash.each do |key, val|
|
143
|
+
result[key.to_s] = val
|
144
|
+
end
|
145
|
+
result
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|