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.
@@ -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
- # A cloud event data type.
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
- # This object represents both the event data and the context attributes.
24
- # It is immutable. The data and attribute values can be retrieved but not
25
- # modified. To obtain an event with modifications, use the {#with} method
26
- # to create a copy with the desired changes.
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
- # See https://github.com/cloudevents/spec/blob/master/spec.md for
29
- # descriptions of the various attributes.
42
+ # To create an event, you may either:
30
43
  #
31
- class Event
32
- ##
33
- # Create a new cloud event object with the given data and attributes.
34
- #
35
- # @param id [String] The required `id` field
36
- # @param source [String,URI] The required `source` field
37
- # @param type [String] The required `type` field
38
- # @param spec_version [String] The required `specversion` field
39
- # @param data [String,Boolean,Integer,Array,Hash] The optional `data`
40
- # field
41
- # @param data_content_type [String,FunctionsFramework::CloudEvents::ContentType]
42
- # The optional `datacontenttype` field
43
- # @param data_schema [String,URI] The optional `dataschema` field
44
- # @param subject [String] The optional `subject` field
45
- # @param time [String,DateTime] The optional `time` field
46
- #
47
- def initialize \
48
- id:,
49
- source:,
50
- type:,
51
- spec_version:,
52
- data: nil,
53
- data_content_type: nil,
54
- data_schema: nil,
55
- subject: nil,
56
- time: nil
57
- @id = interpret_string "id", id, true
58
- @source, @source_string = interpret_uri "source", source, true
59
- @type = interpret_string "type", type, true
60
- @spec_version = interpret_string "spec_version", spec_version, true
61
- @data = data
62
- @data_content_type, @data_content_type_string =
63
- interpret_content_type "data_content_type", data_content_type
64
- @data_schema, @data_schema_string = interpret_uri "data_schema", data_schema
65
- @subject = interpret_string "subject", subject
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