functions_framework 0.1.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -16,6 +16,7 @@ require "logger"
16
16
 
17
17
  require "functions_framework/cloud_events"
18
18
  require "functions_framework/function"
19
+ require "functions_framework/legacy_event_converter"
19
20
  require "functions_framework/registry"
20
21
  require "functions_framework/version"
21
22
 
@@ -36,8 +37,8 @@ require "functions_framework/version"
36
37
  # functions framework. Use the {FunctionsFramework.http},
37
38
  # {FunctionsFramework.event}, or {FunctionsFramework.cloud_event} methods to
38
39
  # define functions. To serve functions via a web service, invoke the
39
- # `functions-framework` executable, or use the {FunctionsFramework.start} or
40
- # {FunctionsFramework.run} methods.
40
+ # `functions-framework-ruby` executable, or use the {FunctionsFramework.start}
41
+ # or {FunctionsFramework.run} methods.
41
42
  #
42
43
  # ## Internal modules
43
44
  #
@@ -48,7 +49,7 @@ require "functions_framework/version"
48
49
  # you define an event function, you will receive the event as a
49
50
  # {FunctionsFramework::CloudEvents::Event} object.
50
51
  # * {FunctionsFramework::CLI} is the implementation of the
51
- # `functions-framework` executable. Most apps will not need to interact
52
+ # `functions-framework-ruby` executable. Most apps will not need to interact
52
53
  # with this class directly.
53
54
  # * {FunctionsFramework::Function} is the internal representation of a
54
55
  # function, indicating the type of function (http or cloud event), the
@@ -62,7 +63,7 @@ require "functions_framework/version"
62
63
  # * {FunctionsFramework::Server} is a web server that makes a function
63
64
  # available via HTTP. It wraps the Puma web server and runs a specific
64
65
  # {FunctionsFramework::Function}. Many apps can simply run the
65
- # `functions-framework` executable to spin up a server. However, if you
66
+ # `functions-framework-ruby` executable to spin up a server. However, if you
66
67
  # need closer control over your execution environment, you can use the
67
68
  # {FunctionsFramework::Server} class to run a server. Note that, in most
68
69
  # cases, it is easier to use the {FunctionsFramework.start} or
@@ -139,37 +140,10 @@ module FunctionsFramework
139
140
  end
140
141
 
141
142
  ##
142
- # Define a function that responds to CloudEvents.
143
- #
144
- # You must provide a name for the function, and a block that implemets the
145
- # function. The block should take two arguments: the event _data_ and the
146
- # event _context_. Any return value is ignored.
147
- #
148
- # The event data argument will be one of the following types:
149
- # * A `String` (with encoding `ASCII-8BIT`) if the data is in the form of
150
- # binary data. You may choose to perform additional interpretation of
151
- # the binary data using information in the content type provided by the
152
- # context argument.
153
- # * Any data type that can be represented in JSON (i.e. `String`,
154
- # `Integer`, `Array`, `Hash`, `true`, `false`, or `nil`) if the event
155
- # came with a JSON payload. The content type may also be set in the
156
- # context if the data is a String.
157
- #
158
- # The context argument will be of type {FunctionsFramework::CloudEvents::Event},
159
- # and will contain CloudEvents context attributes such as `id` and `type`.
160
- #
161
- # See also {FunctionsFramework.cloud_event} which defines a function that
162
- # takes a single argument of type {FunctionsFramework::CloudEvents::Event}.
163
- #
164
- # ## Example
143
+ # This is an obsolete interface that defines an event function taking two
144
+ # arguments (data and context) rather than one.
165
145
  #
166
- # FunctionsFramework.event "my-function" do |data, context|
167
- # FunctionsFramework.logger.info "Event data: #{data.inspect}"
168
- # end
169
- #
170
- # @param name [String] The function name. Defaults to {DEFAULT_TARGET}.
171
- # @param block [Proc] The function code as a proc.
172
- # @return [self]
146
+ # @deprecated Use {FunctionsFramework.cloud_event} instead.
173
147
  #
174
148
  def event name = DEFAULT_TARGET, &block
175
149
  global_registry.add_event name, &block
@@ -180,12 +154,9 @@ module FunctionsFramework
180
154
  # Define a function that responds to CloudEvents.
181
155
  #
182
156
  # You must provide a name for the function, and a block that implemets the
183
- # function. The block should take _one_ argument: the event object of type
157
+ # function. The block should take one argument: the event object of type
184
158
  # {FunctionsFramework::CloudEvents::Event}. Any return value is ignored.
185
159
  #
186
- # See also {FunctionsFramework.event} which creates a function that takes
187
- # data and context as separate arguments.
188
- #
189
160
  # ## Example
190
161
  #
191
162
  # FunctionsFramework.cloud_event "my-function" do |event|
@@ -205,15 +176,20 @@ module FunctionsFramework
205
176
  # Start the functions framework server in the background. The server will
206
177
  # look up the given target function name in the global registry.
207
178
  #
208
- # @param target [String] The name of the function to run
179
+ # @param target [FunctionsFramework::Function,String] The function to run,
180
+ # or the name of the function to look up in the global registry.
209
181
  # @yield [FunctionsFramework::Server::Config] A config object that can be
210
182
  # manipulated to configure the server.
211
183
  # @return [FunctionsFramework::Server]
212
184
  #
213
185
  def start target, &block
214
186
  require "functions_framework/server"
215
- function = global_registry[target]
216
- raise ::ArgumentError, "Undefined function: #{target.inspect}" if function.nil?
187
+ if target.is_a? ::FunctionsFramework::Function
188
+ function = target
189
+ else
190
+ function = global_registry[target]
191
+ raise ::ArgumentError, "Undefined function: #{target.inspect}" if function.nil?
192
+ end
217
193
  server = Server.new function, &block
218
194
  server.respond_to_signals
219
195
  server.start
@@ -223,7 +199,8 @@ module FunctionsFramework
223
199
  # Run the functions framework server and block until it stops. The server
224
200
  # will look up the given target function name in the global registry.
225
201
  #
226
- # @param target [String] The name of the function to run
202
+ # @param target [FunctionsFramework::Function,String] The function to run,
203
+ # or the name of the function to look up in the global registry.
227
204
  # @yield [FunctionsFramework::Server::Config] A config object that can be
228
205
  # manipulated to configure the server.
229
206
  # @return [self]
@@ -12,15 +12,22 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ require "logger"
15
16
  require "optparse"
16
17
 
17
18
  require "functions_framework"
18
19
 
19
20
  module FunctionsFramework
20
21
  ##
21
- # Implementation of the functions-framework executable.
22
+ # Implementation of the functions-framework-ruby executable.
22
23
  #
23
24
  class CLI
25
+ ##
26
+ # The default logging level, if not given in the environment variable.
27
+ # @return [Integer]
28
+ #
29
+ DEFAULT_LOGGING_LEVEL = ::Logger::Severity::INFO
30
+
24
31
  ##
25
32
  # Create a new CLI, setting arguments to their defaults.
26
33
  #
@@ -33,6 +40,8 @@ module FunctionsFramework
33
40
  @min_threads = nil
34
41
  @max_threads = nil
35
42
  @detailed_errors = nil
43
+ @signature_type = ::ENV["FUNCTION_SIGNATURE_TYPE"]
44
+ @logging_level = init_logging_level
36
45
  end
37
46
 
38
47
  ##
@@ -52,6 +61,11 @@ module FunctionsFramework
52
61
  "Set the source file to load (defaults to #{DEFAULT_SOURCE})" do |val|
53
62
  @source = val
54
63
  end
64
+ op.on "--signature-type TYPE",
65
+ "Asserts that the function has the given signature type." \
66
+ " Supported values are 'http' and 'cloudevent'." do |val|
67
+ @signature_type = val
68
+ end
55
69
  op.on "-p", "--port PORT", "Set the port to listen to (defaults to 8080)" do |val|
56
70
  @port = val.to_i
57
71
  end
@@ -71,10 +85,10 @@ module FunctionsFramework
71
85
  @detailed_errors = val
72
86
  end
73
87
  op.on "-v", "--verbose", "Increase log verbosity" do
74
- ::FunctionsFramework.logger.level -= 1
88
+ @logging_level -= 1
75
89
  end
76
90
  op.on "-q", "--quiet", "Decrease log verbosity" do
77
- ::FunctionsFramework.logger.level += 1
91
+ @logging_level += 1
78
92
  end
79
93
  op.on "--help", "Display help" do
80
94
  puts op
@@ -82,23 +96,51 @@ module FunctionsFramework
82
96
  end
83
97
  end
84
98
  option_parser.parse! argv
85
- unless argv.empty?
86
- warn "Unrecognized arguments: #{argv}"
87
- puts op
88
- exit 1
89
- end
99
+ error "Unrecognized arguments: #{argv}\n#{op}" unless argv.empty?
90
100
  self
91
101
  end
92
102
 
93
103
  ##
94
104
  # Run the configured server, and block until it stops.
105
+ # If a validation error occurs, print a message and exit.
106
+ #
95
107
  # @return [self]
96
108
  #
97
109
  def run
98
- FunctionsFramework.logger.info \
99
- "FunctionsFramework: Loading functions from #{@source.inspect}..."
110
+ begin
111
+ server = start_server
112
+ rescue ::StandardError => e
113
+ error e.message
114
+ end
115
+ server.wait_until_stopped
116
+ self
117
+ end
118
+
119
+ ##
120
+ # Start the configured server and return the running server object.
121
+ # If a validation error occurs, raise an exception.
122
+ # This is used for testing the CLI.
123
+ #
124
+ # @return [FunctionsFramework::Server]
125
+ #
126
+ # @private
127
+ #
128
+ def start_server
129
+ ::FunctionsFramework.logger.level = @logging_level
130
+ ::FunctionsFramework.logger.info "FunctionsFramework v#{VERSION} server starting."
131
+ ::ENV["FUNCTION_TARGET"] = @target
132
+ ::ENV["FUNCTION_SOURCE"] = @source
133
+ ::ENV["FUNCTION_SIGNATURE_TYPE"] = @signature_type
134
+ ::FunctionsFramework.logger.info "FunctionsFramework: Loading functions from #{@source.inspect}..."
100
135
  load @source
101
- server = ::FunctionsFramework.start @target do |config|
136
+ function = ::FunctionsFramework.global_registry[@target]
137
+ raise "Undefined function: #{@target.inspect}" if function.nil?
138
+ unless @signature_type.nil? ||
139
+ @signature_type == "http" && function.type == :http ||
140
+ ["cloudevent", "event"].include?(@signature_type) && function.type == :cloud_event
141
+ raise "Function #{@target.inspect} does not match type #{@signature_type}"
142
+ end
143
+ ::FunctionsFramework.start function do |config|
102
144
  config.rack_env = @env
103
145
  config.port = @port
104
146
  config.bind_addr = @bind
@@ -106,8 +148,24 @@ module FunctionsFramework
106
148
  config.min_threads = @min_threads
107
149
  config.max_threads = @max_threads
108
150
  end
109
- server.wait_until_stopped
110
- self
151
+ end
152
+
153
+ private
154
+
155
+ def init_logging_level
156
+ level_name = ::ENV["FUNCTION_LOGGING_LEVEL"].to_s.upcase.to_sym
157
+ ::Logger::Severity.const_get level_name
158
+ rescue ::NameError
159
+ DEFAULT_LOGGING_LEVEL
160
+ end
161
+
162
+ ##
163
+ # Print the given error message and exit.
164
+ # @param message [String]
165
+ #
166
+ def error message
167
+ warn message
168
+ exit 1
111
169
  end
112
170
  end
113
171
  end
@@ -12,9 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require "functions_framework/cloud_events/binary_content"
16
15
  require "functions_framework/cloud_events/content_type"
16
+ require "functions_framework/cloud_events/errors"
17
17
  require "functions_framework/cloud_events/event"
18
+ require "functions_framework/cloud_events/http_binding"
19
+ require "functions_framework/cloud_events/json_format"
18
20
 
19
21
  module FunctionsFramework
20
22
  ##
@@ -22,122 +24,20 @@ module FunctionsFramework
22
24
  #
23
25
  # This is a Ruby implementation of the [CloudEvents](https://cloudevents.io)
24
26
  # [1.0 specification](https://github.com/cloudevents/spec/blob/master/spec.md).
25
- # It provides for unmarshaling of events from Rack environment data from
26
- # binary (i.e. header-based) format, as well as structured (body-based) and
27
- # batch formats. A standard JSON structure parser is included. It is also
28
- # possible to register handlers for other formats.
29
- #
30
- # TODO: Unmarshaling of events is implemented, but marshaling is not.
31
27
  #
32
28
  module CloudEvents
33
- @structured_formats = {}
34
- @batched_formats = {}
29
+ # @private
30
+ SUPPORTED_SPEC_VERSIONS = ["1.0"].freeze
35
31
 
36
32
  class << self
37
33
  ##
38
- # Register a handler for the given structured format.
39
- # The handler object must respond to the method
40
- # `#decode_structured_content`. See
41
- # {FunctionsFramework::CloudEvents::JsonStructure} for an example.
42
- #
43
- # @param format [String] The subtype format that should be handled by
44
- # this handler
45
- # @param handler [#decode_structured_content] The handler object
46
- # @return [self]
47
- #
48
- def register_structured_format format, handler
49
- handlers = @structured_formats[format.to_s.strip.downcase] ||= []
50
- handlers << handler unless handlers.include? handler
51
- self
52
- end
53
-
54
- ##
55
- # Register a handler for the given batched format.
56
- # The handler object must respond to the method
57
- # `#decode_batched_content`. See
58
- # {FunctionsFramework::CloudEvents::JsonStructure} for an example.
34
+ # The spec versions supported by this implementation.
59
35
  #
60
- # @param format [String] The subtype format that should be handled by
61
- # this handler
62
- # @param handler [#decode_batched_content] The handler object
63
- # @return [self]
36
+ # @return [Array<String>]
64
37
  #
65
- def register_batched_format format, handler
66
- handlers = @batched_formats[format.to_s.strip.downcase] ||= []
67
- handlers << handler unless handlers.include? handler
68
- self
69
- end
70
-
71
- ##
72
- # Decode an event from the given Rack environment hash. Following the
73
- # CloudEvents spec, this chooses a handler based on the Content-Type of
74
- # the request.
75
- #
76
- # @param env [Hash] The Rack environment
77
- # @return [FunctionsFramework::CloudEvents::Event] if the request
78
- # includes a single structured or binary event
79
- # @return [Array<FunctionsFramework::CloudEvents::Event>] if the request
80
- # includes a batch of structured events
81
- #
82
- def decode_rack_env env
83
- content_type_header = env["CONTENT_TYPE"]
84
- raise "Missing content-type header" unless content_type_header
85
- content_type = ContentType.new content_type_header
86
- if content_type.media_type == "application"
87
- case content_type.subtype_prefix
88
- when "cloudevents"
89
- return decode_structured_content env["rack.input"], content_type
90
- when "cloudevents-batch"
91
- return decode_batched_content env["rack.input"], content_type
92
- end
93
- end
94
- BinaryContent.decode_rack_env env, content_type
95
- end
96
-
97
- ##
98
- # Decode a single event from the given content data. This should be
99
- # passed the request body, if the Content-Type is of the form
100
- # `application/cloudevents+format`.
101
- #
102
- # @param input [IO] An IO-like object providing the content
103
- # @param content_type [FunctionsFramework::CloudEvents::ContentType] the
104
- # content type
105
- # @return [FunctionsFramework::CloudEvents::Event]
106
- #
107
- def decode_structured_content input, content_type
108
- handlers = @structured_formats[content_type.subtype_format] || []
109
- handlers.reverse_each do |handler|
110
- event = handler.decode_structured_content input, content_type
111
- return event if event
112
- end
113
- raise "Unknown cloudevents format: #{content_type.subtype_format.inspect}"
114
- end
115
-
116
- ##
117
- # Decode a batch of events from the given content data. This should be
118
- # passed the request body, if the Content-Type is of the form
119
- # `application/cloudevents-batch+format`.
120
- #
121
- # @param input [IO] An IO-like object providing the content
122
- # @param content_type [FunctionsFramework::CloudEvents::ContentType] the
123
- # content type
124
- # @return [Array<FunctionsFramework::CloudEvents::Event>]
125
- #
126
- def decode_batched_content input, content_type
127
- handlers = @batched_formats[content_type.subtype_format] || []
128
- handlers.reverse_each do |handler|
129
- events = handler.decode_batched_content input, content_type
130
- return events if events
131
- end
132
- raise "Unknown cloudevents batch format: #{content_type.subtype_format.inspect}"
38
+ def supported_spec_versions
39
+ SUPPORTED_SPEC_VERSIONS
133
40
  end
134
41
  end
135
42
  end
136
43
  end
137
-
138
- require "functions_framework/cloud_events/json_structure"
139
-
140
- FunctionsFramework::CloudEvents.register_structured_format \
141
- "json", FunctionsFramework::CloudEvents::JsonStructure
142
- FunctionsFramework::CloudEvents.register_batched_format \
143
- "json", FunctionsFramework::CloudEvents::JsonStructure
@@ -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 < ::RuntimeError
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,64 @@
15
15
  require "date"
16
16
  require "uri"
17
17
 
18
+ require "functions_framework/cloud_events/event/v1"
19
+
18
20
  module FunctionsFramework
19
21
  module CloudEvents
20
22
  ##
21
- # A cloud event data type.
23
+ # CloudEvent object.
22
24
  #
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.
25
+ # An Event object represents a complete event, including both its data and
26
+ # its context attributes. The following are true of all event objects:
27
27
  #
28
- # See https://github.com/cloudevents/spec/blob/master/spec.md for
29
- # descriptions of the various attributes.
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.)
30
41
  #
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}"
42
+ # To create an event, you may either:
43
+ #
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/blob/master/spec.md for more
53
+ # information about CloudEvents.
54
+ #
55
+ module Event
56
+ class << self
57
+ ##
58
+ # Create a new cloud event object with the given version. Generally,
59
+ # you must also pass additional keyword arguments providing the event's
60
+ # data and attributes. For example, if you pass `1.0` as the
61
+ # `spec_version`, the remaining keyword arguments will be passed
62
+ # through to the {Event::V1.new} constructor.
63
+ #
64
+ # @param spec_version [String] The required `specversion` field.
65
+ # @param kwargs [keywords] Additional parameters for the event.
66
+ #
67
+ def create spec_version:, **kwargs
68
+ case spec_version
69
+ when /^1(\.|$)/
70
+ V1.new spec_version: spec_version, **kwargs
71
+ else
72
+ raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
73
+ end
273
74
  end
75
+ alias new create
274
76
  end
275
77
  end
276
78
  end