functions_framework 0.1.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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