carrot_rpc 0.2.3.pre

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,110 @@
1
+ # Command-line interface for {CarrotRpc}
2
+ module CarrotRpc::CLI
3
+ def self.add_common_options(option_parser)
4
+ option_parser.separator ""
5
+
6
+ option_parser.separator "Common options:"
7
+ option_parser.on("-h", "--help") do
8
+ puts option_parser.to_s
9
+ exit
10
+ end
11
+
12
+ option_parser.on("-v", "--version") do
13
+ puts CarrotRpc::VERSION
14
+ exit
15
+ end
16
+ end
17
+
18
+ # There are just too many options in the Process options category and they can't really be broken down more
19
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
20
+
21
+ # Add "Process options" to `option_parser`.
22
+ #
23
+ # @param option_parser [OptionParser]
24
+ # @return [OptionParser]
25
+ def self.add_process_options(option_parser)
26
+ option_parser.separator ""
27
+
28
+ option_parser.separator "Process options:"
29
+ option_parser.on("-d", "--daemonize", "run daemonized in the background (default: false)") do
30
+ CarrotRpc.configuration.daemonize = true
31
+ end
32
+
33
+ option_parser.on(" ", "--pidfile PIDFILE", "the pid filename") do |value|
34
+ CarrotRpc.configuration.pidfile = value
35
+ end
36
+
37
+ option_parser.on("-s", "--runloop_sleep VALUE", Float, "Configurable sleep time in the runloop") do |value|
38
+ CarrotRpc.configuration.runloop_sleep = value
39
+ end
40
+
41
+ option_parser.on(
42
+ " ",
43
+ "--autoload_rails value",
44
+ "loads rails env by default. Uses Rails Logger by default."
45
+ ) do |value|
46
+ pv = value == "false" ? false : true
47
+ CarrotRpc.configuration.autoload_rails = pv
48
+ end
49
+
50
+ option_parser.on(" ", "--logfile VALUE", "relative path and name for Log file. Overrides Rails logger.") do |value|
51
+ CarrotRpc.configuration.logfile = File.expand_path("../../#{value}", __FILE__)
52
+ end
53
+
54
+ option_parser.on(
55
+ " ",
56
+ "--loglevel VALUE",
57
+ "levels of loggin: DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN"
58
+ ) do |value|
59
+ CarrotRpc.configuration.loglevel = Logger.const_get(value) || 0
60
+ end
61
+
62
+ # Optional. Defaults to using the ENV['RABBITMQ_URL']
63
+ option_parser.on(
64
+ " ",
65
+ "--rabbitmq_url VALUE",
66
+ "connection string to RabbitMQ 'amqp://user:pass@host:10000/vhost'"
67
+ ) do |value|
68
+ CarrotRpc.configuration.bunny = Bunny.new(value)
69
+ end
70
+ end
71
+
72
+ def self.add_ruby_options(option_parser)
73
+ option_parser.separator ""
74
+
75
+ option_parser.separator "Ruby options:"
76
+
77
+ option_parser.on("-I", "--include PATH", "an additional $LOAD_PATH") do |value|
78
+ $LOAD_PATH.unshift(*value.split(":").map { |v| File.expand_path(v) })
79
+ end
80
+
81
+ option_parser.on("--debug", "set $DEBUG to true") do
82
+ $DEBUG = true
83
+ end
84
+
85
+ option_parser.on("--warn", "enable warnings") do
86
+ $-w = true
87
+ end
88
+ end
89
+
90
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
91
+
92
+ def self.option_parser
93
+ option_parser = OptionParser.new
94
+ option_parser.banner = "RPC Server Runner for RabbitMQ RPC Services."
95
+ option_parser.separator ""
96
+ option_parser.separator "Usage: server [options]"
97
+
98
+ add_process_options(option_parser)
99
+ add_ruby_options(option_parser)
100
+ add_common_options(option_parser)
101
+
102
+ option_parser.separator ""
103
+
104
+ option_parser
105
+ end
106
+
107
+ def self.parse_options(args = ARGV)
108
+ option_parser.parse!(args)
109
+ end
110
+ end
@@ -0,0 +1,25 @@
1
+ # Common functionality for Client and Server.
2
+ module CarrotRpc::ClientServer
3
+ # @overload queue_name(new_name)
4
+ # @note Default naming not performed. Class must pass queue name.
5
+ #
6
+ # Allows for class level definition of queue name.
7
+ #
8
+ # @param new_name [String] the queue name for the class.
9
+ # @return [String] `new_name`
10
+ #
11
+ # @overload queue_name
12
+ # The current queue name previously set with `#queue_name(new_name)`.
13
+ #
14
+ # @return [String]
15
+ def queue_name(*args)
16
+ if args.length == 0
17
+ @queue_name
18
+ elsif args.length == 1
19
+ @queue_name = args[0]
20
+ else
21
+ fail ArgumentError,
22
+ "queue_name(new_name) :: new_name or queue_name() :: current_name are the only ways to call queue_name"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # Global configuration for {CarrotRpc}. Access with {CarrotRpc.configuration}.
2
+ class CarrotRpc::Configuration
3
+ attr_accessor :logger, :logfile, :loglevel, :daemonize, :pidfile, :runloop_sleep, :autoload_rails, :bunny
4
+
5
+ # logfile - set logger to a file. overrides rails logger.
6
+
7
+ def initialize
8
+ @logfile = nil
9
+ @loglevel = Logger::DEBUG
10
+ @logger = nil
11
+ @daemonize = false
12
+ @pidfile = nil
13
+ @runloop_sleep = 0
14
+ @autoload_rails = true
15
+ @bunny = nil
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # An enum of predefined {RpcServer::Server#code}
2
+ module CarrotRpc::Error::Code
3
+ # Internal JSON-RPC error.
4
+ INTERNAL_ERROR = -32_603
5
+
6
+ # Invalid method parameter(s).
7
+ INVALID_PARAMS = -32_602
8
+
9
+ # The JSON sent is not a valid Request object.
10
+ INVALID_REQUEST = -32_600
11
+
12
+ # The method does not exist / is not available.
13
+ METHOD_NOT_FOUND = -32_601
14
+
15
+ # Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
16
+ PARSE_ERROR = -32_700
17
+ end
@@ -0,0 +1,43 @@
1
+ # Error raised by an {RpcServer} method to signal that a
2
+ # {http://www.jsonrpc.org/specification#error_object JSON RPC 2.0 Response Error object} should be the reply.
3
+ class CarrotRpc::Error < StandardError
4
+ autoload :Code, "carrot_rpc/error/code"
5
+
6
+ # @return [Integer]A Number that indicates the error type that occurred. Some codes are
7
+ # {http://www.jsonrpc.org/specification#error_object predefined}.
8
+ attr_reader :code
9
+
10
+ # @return [Object, nil] A Primitive or Structured value that contains additional information about the error.
11
+ # This may be omitted. The value of this member is defined by the Server (e.g. detailed error information,
12
+ # nested errors etc.).
13
+ attr_reader :data
14
+
15
+ # @param code [Integer] A Number that indicates the error type that occurred. Favor using the
16
+ # {http://www.jsonrpc.org/specification#error_object predefined codes}.
17
+ # @param message [String] A String providing a short description of the error. The message SHOULD be limited to a
18
+ # concise single sentence.
19
+ # @param data [Object, nil] A Primitive or Structured value that contains additional information about the error.
20
+ # This may be omitted. The value of this member is defined by the Server (e.g. detailed error information,
21
+ # nested errors etc.).
22
+ def initialize(code:, message:, data: nil)
23
+ @code = code
24
+ @data = data
25
+ super(message)
26
+ end
27
+
28
+ # A properly formatted {http://www.jsonrpc.org/specification#error_object JSON RPC Error object}.
29
+ #
30
+ # @return [Hash{code: String, message: String}, Hash{code: String, data: Object, message: String}]
31
+ def serialized_message
32
+ serialized = {
33
+ code: code,
34
+ message: message
35
+ }
36
+
37
+ if data
38
+ serialized[:data] = data
39
+ end
40
+
41
+ serialized
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # Refine the Hash class with new methods and functionality.
2
+ module CarrotRpc::HashExtensions
3
+ refine Hash do
4
+ # Utility method to rename keys in a hash
5
+ # @param [String] find the text to look for in a keys
6
+ # @param [String] replace the text to replace the found text
7
+ # @return [Hash] a new hash
8
+ def rename_keys(find, replace, new_hash = {})
9
+ each do |k, v|
10
+ new_key = k.to_s.gsub(find, replace)
11
+
12
+ new_hash[new_key] = if v.is_a? Hash
13
+ v.rename_keys(find, replace)
14
+ else
15
+ v
16
+ end
17
+ end
18
+
19
+ new_hash
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,117 @@
1
+ require "securerandom"
2
+
3
+ # Generic class for all RPC Consumers. Use as a base class to build other RPC Consumers for related functionality.
4
+ # Let's define a naming convention here for subclasses becuase I don't want to write a Confluence doc.
5
+ # All subclasses should have the following naming convention: <Name>RpcConsumer ex: PostRpcConsumer
6
+ class CarrotRpc::RpcClient
7
+ using CarrotRpc::HashExtensions
8
+
9
+ attr_reader :channel, :server_queue, :logger
10
+
11
+ extend CarrotRpc::ClientServer
12
+
13
+ # Use defaults for application level connection to RabbitMQ
14
+ # All RPC data goes over the same queue. I think that's ok....
15
+ def initialize(config: nil)
16
+ config ||= CarrotRpc.configuration
17
+ @channel = config.bunny.create_channel
18
+ @logger = config.logger
19
+ # auto_delete => false keeps the queue around until RabbitMQ restarts or explicitly deleted
20
+ @server_queue = @channel.queue(self.class.queue_name, auto_delete: false)
21
+
22
+ # Setup a direct exchange.
23
+ @exchange = @channel.default_exchange
24
+ end
25
+
26
+ # Starts the connection to listen for messages.
27
+ def start
28
+ # Empty queue name ends up creating a randomly named queue by RabbitMQ
29
+ # Exclusive => queue will be deleted when connection closes. Allows for automatic "cleanup".
30
+ @reply_queue = @channel.queue("", exclusive: true)
31
+
32
+ # setup a hash for results with a Queue object as a value
33
+ @results = Hash.new { |h, k| h[k] = Queue.new }
34
+
35
+ # setup subscribe block to Service
36
+ # block => false is a non blocking IO option.
37
+ @reply_queue.subscribe(block: false) do |_delivery_info, properties, payload|
38
+ result = JSON.parse(payload).rename_keys("-", "_").with_indifferent_access
39
+ @results[properties[:correlation_id]].push(result[:result])
40
+ end
41
+ end
42
+
43
+ # params is an array of method argument values
44
+ # programmer implementing this class must know about the remote service
45
+ # the remote service must have documented the methods and arguments in order for this pattern to work.
46
+ # TODO: change to a hash to account for keyword arguments???
47
+ #
48
+ # @param remote_method [String, Symbol] the method to be called on current receiver
49
+ # @param params [Hash] the arguments for the method being called.
50
+ # @return [Object] the result of the method call.
51
+ def remote_call(remote_method, params)
52
+ correlation_id = SecureRandom.uuid
53
+ publish(correlation_id: correlation_id, method: remote_method, params: params.rename_keys("_", "-"))
54
+ wait_for_result(correlation_id)
55
+ end
56
+
57
+ def wait_for_result(correlation_id)
58
+ # `pop` is `Queue#pop`, so it is blocking on the receiving thread and this must happend before the `Hash.delete` or
59
+ # the receiving thread won't be able to find the correlation_id in @results
60
+ result = @results[correlation_id].pop
61
+ @results.delete correlation_id # remove item from hash. prevents memory leak.
62
+ result
63
+ end
64
+
65
+ def publish(correlation_id:, method:, params:)
66
+ message = message(
67
+ correlation_id: correlation_id,
68
+ params: params,
69
+ method: method
70
+ )
71
+ # Reply To => make sure the service knows where to send it's response.
72
+ # Correlation ID => identify the results that belong to the unique call made
73
+ @exchange.publish(message.to_json, routing_key: @server_queue.name, correlation_id: correlation_id,
74
+ reply_to: @reply_queue.name)
75
+ end
76
+
77
+ def message(correlation_id:, method:, params:)
78
+ {
79
+ id: correlation_id,
80
+ jsonrpc: "2.0",
81
+ method: method,
82
+ params: params.except(:controller, :action)
83
+ }
84
+ end
85
+
86
+ # Convience method as a resource alias for index action.
87
+ # To customize, override the method in your class.
88
+ #
89
+ # @param params [Hash] the arguments for the method being called.
90
+ def index(params)
91
+ remote_call("index", params)
92
+ end
93
+
94
+ # Convience method as a resource alias for show action.
95
+ # To customize, override the method in your class.
96
+ #
97
+ # @param params [Hash] the arguments for the method being called.
98
+ def show(params)
99
+ remote_call("show", params)
100
+ end
101
+
102
+ # Convience method as a resource alias for create action.
103
+ # To customize, override the method in your class.
104
+ #
105
+ # @param params [Hash] the arguments for the method being called.
106
+ def create(params)
107
+ remote_call("create", params)
108
+ end
109
+
110
+ # Convience method as a resource alias for update action.
111
+ # To customize, override the method in your class.
112
+ #
113
+ # @param params [Hash] the arguments for the method being called.
114
+ def update(params)
115
+ remote_call("update", params)
116
+ end
117
+ end
@@ -0,0 +1,108 @@
1
+ # The common CRUD actions for {CarrotRpc::RpcServer::JSONAPIResources}
2
+ module CarrotRpc::RpcServer::JSONAPIResources::Actions
3
+ #
4
+ # CONSTANTS
5
+ #
6
+
7
+ # Set of allowed actions for JSONAPI::Resources
8
+ NAME_SET = Set.new(
9
+ [
10
+ # Mimic behaviour of `POST <collection>` routes
11
+ :create,
12
+ # Mimic behaviour of `POST <collection>/<id>/relationships/<relation>` routes
13
+ :create_relationship,
14
+ # Mimic behavior of `DELETE <collection>/<id>` routes
15
+ :destroy,
16
+ # Mimic behavior of `DELETE <collection>/<id>/relationships/<relationship>` routes
17
+ :destroy_relationship,
18
+ # Mimics behavior of `GET <collection>/<id>/<relationship>` routes
19
+ :get_related_resource,
20
+ # Mimic behavior of `GET <collection>` routes
21
+ :index,
22
+ # Mimic behavior of `GET <collection>/<id>` routes
23
+ :show,
24
+ # Mimic behavior of `GET <collection>/<id>/relationships/<relationship>` routes
25
+ :show_relationship,
26
+ # Mimic behavior of `PATCH|PUT <collection>/<id>` routes
27
+ :update,
28
+ # Mimic behavior of `PATCH|PUT <collection>/<id>/relationships/<relationship>` routes
29
+ :update_relationship
30
+ ]
31
+ ).freeze
32
+
33
+ #
34
+ # Module Methods
35
+ #
36
+
37
+ # Defines an action method, `name` on `action_module`.
38
+ #
39
+ # @param action_module [Module] Module where action methods are defined so that they can be called with `super` if
40
+ # overridden.
41
+ # @param name [Symbol] an element of `NAME_SET`.
42
+ # @return [void]
43
+ def self.define_action_method(action_module, name)
44
+ action_module.send(:define_method, name) do |params|
45
+ process_request_params(
46
+ ActionController::Parameters.new(
47
+ params.merge(
48
+ action: name,
49
+ controller: controller
50
+ )
51
+ )
52
+ )
53
+ end
54
+ end
55
+
56
+ #
57
+ # Instance Methods
58
+ #
59
+
60
+ # Adds actions in `names` to the current class.
61
+ #
62
+ # The actions are added to a mixin module `self::Actions`, so that the action methods can be overridden and `super`
63
+ # will work.
64
+ #
65
+ # @example Adding only show actions
66
+ # extend RpcServer::JSONAPIResources::Actions
67
+ # include RpcServer::JSONAPIResources
68
+ #
69
+ # actions :create,
70
+ # :destroy,
71
+ # :index,
72
+ # :show,
73
+ # :update
74
+ #
75
+ # @param names [Array<Symbol>] a array of a subset of {NAME_SET}.
76
+ # @return [void]
77
+ # @raise (see #valid_actions)
78
+ def actions(*names)
79
+ valid_actions!(names)
80
+
81
+ # an include module so that `super` works if the method is overridden
82
+ action_module = Module.new
83
+
84
+ names.each do |name|
85
+ CarrotRpc::RpcServer::JSONAPIResources::Actions.define_action_method(action_module, name)
86
+ end
87
+
88
+ const_set(:Actions, action_module)
89
+
90
+ include action_module
91
+ end
92
+
93
+ private
94
+
95
+ # Checks that all `names` are valid action names.
96
+ #
97
+ # @raise [ArgumentError] if any element of `names` is not an element of `NAME_SET`.
98
+ def valid_actions!(names)
99
+ given_name_set = Set.new(names)
100
+ unknown_name_set = given_name_set - NAME_SET
101
+
102
+ unless unknown_name_set.empty?
103
+ fail ArgumentError,
104
+ "#{unknown_name_set.to_a.sort.to_sentence} are not elements of known actions " \
105
+ "(#{NAME_SET.to_a.sort.to_sentence})"
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Allows a {CarrotRpc::RpcServer} subclass to behave the same as a controller that does
4
+ # `include JSONAPI::ActsAsResourceController`
5
+ module CarrotRpc::RpcServer::JSONAPIResources
6
+ autoload :Actions, "carrot_rpc/rpc_server/jsonapi_resources/actions"
7
+
8
+ # The base "meta" to include in the top-level of all JSON API documents.
9
+ #
10
+ # @param request [JSONAPI::Request] the current request. `JSONAPI::Request#warnings` are merged into the
11
+ # {#base_response_meta}.
12
+ # @return [Hash]
13
+ def base_meta(request)
14
+ if request.nil? || request.warnings.empty?
15
+ base_response_meta
16
+ else
17
+ base_response_meta.merge(warnings: request.warnings)
18
+ end
19
+ end
20
+
21
+ # The base "links" to include the top-level of all JSON API documents before any operation result links are added.
22
+ #
23
+ # @return [Hash] Defaults to `{}`.
24
+ def base_response_links
25
+ {}
26
+ end
27
+
28
+ # The base "meta" to include in the top-level of all JSON API documents before being merged with any request warnings
29
+ # in {#base_meta}.
30
+ #
31
+ # @return [Hash] Defaults to `{}`.
32
+ def base_response_meta
33
+ {}
34
+ end
35
+
36
+ # The operations processor in the configuration or override this to use another operations processor
37
+ #
38
+ # @return [JSONAPI::OperationsProcessor]
39
+ def create_operations_processor
40
+ JSONAPI.configuration.operations_processor.new
41
+ end
42
+
43
+ # The JSON API Document for the `operation_results` and `request`.
44
+ #
45
+ # @param operation_results [JSONAPI::OperationResults] The result of processing the `request`.
46
+ # @param request [JSONAPI::Request] the request to respond to.
47
+ # @return [JSONAPI::ResponseDocument]
48
+ def create_response_document(operation_results:, request:) # rubocop:disable Metrics/MethodLength
49
+ JSONAPI::ResponseDocument.new(
50
+ operation_results,
51
+ primary_resource_klass: resource_klass,
52
+ include_directives: request ? request.include_directives : nil,
53
+ fields: request ? request.fields : nil,
54
+ base_url: base_url,
55
+ key_formatter: key_formatter,
56
+ route_formatter: route_formatter,
57
+ base_meta: base_meta(request),
58
+ base_links: base_response_links,
59
+ resource_serializer_klass: resource_serializer_klass,
60
+ request: request,
61
+ serialization_options: serialization_options
62
+ )
63
+ end
64
+
65
+ # @note Override this to process other exceptions. Be sure to either call `super(exception)` or handle
66
+ # `JSONAPI::Exceptions::Error` and `raise` unhandled exceptions.
67
+ #
68
+ # @param exception [Exception] the original, exception that was caught
69
+ # @param request [JSONAPI::Request] the request that triggered the `exception`
70
+ # @return (see #render_errors)
71
+ def handle_exceptions(exception, request:)
72
+ case exception
73
+ when JSONAPI::Exceptions::Error
74
+ render_errors(exception.errors, request: request)
75
+ else
76
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(exception)
77
+ logger.error { # rubocop:disable Style/BlockDelimiters
78
+ "Internal Server Error: #{exception.message} #{exception.backtrace.join("\n")}"
79
+ }
80
+ render_errors(internal_server_error.errors)
81
+ end
82
+ end
83
+
84
+ # @note Override if you want to set a per controller key format.
85
+ #
86
+ # Control by setting in an initializer:
87
+ # JSONAPI.configuration.json_key_format = :camelized_key
88
+ #
89
+ # @return [JSONAPI::KeyFormatter]
90
+ def key_formatter
91
+ JSONAPI.configuration.key_formatter
92
+ end
93
+
94
+ # Processes the params as a request and renders a response.
95
+ #
96
+ # @param params [ActionController::Parameter{action: Symbol, controller: String}] **MUST** set `:action` to the action
97
+ # name so that `JSONAPI::Request#setup_action` can dispatch to the correct `setup_*_action` method. **MUST** set
98
+ # `:controller` to a URL name for the controller, such as `"api/v1/partner"`, so that the resource can be looked up
99
+ # by the controller name.
100
+ # @return [Hash] rendered, but not encoded JSON.
101
+ def process_request_params(params) # rubocop:disable Metrics/MethodLength
102
+ request = JSONAPI::Request.new(
103
+ params,
104
+ context: {},
105
+ key_formatter: key_formatter,
106
+ server_error_callbacks: []
107
+ )
108
+
109
+ if !request.errors.empty?
110
+ render_errors(request.errors, request: request)
111
+ else
112
+ operation_results = create_operations_processor.process(request)
113
+ render_results(
114
+ operation_results: operation_results,
115
+ request: request
116
+ )
117
+ end
118
+ rescue => e
119
+ handle_exceptions(e, request: request)
120
+ end
121
+
122
+ # Renders the `errors` as a JSON API errors Document.
123
+ #
124
+ # @param errors [Array<JSONAPI::Error>] errors to use in a JSON API errors Document
125
+ # @param request [JSONAPI::Request] the request that caused the `errors`.
126
+ # @return [Hash] rendered, but not encoded JSON
127
+ def render_errors(errors, request:)
128
+ operation_results = JSONAPI::OperationResults.new
129
+ result = JSONAPI::ErrorsOperationResult.new(errors[0].status, errors)
130
+ operation_results.add_result(result)
131
+
132
+ render_results(
133
+ operation_results: operation_results,
134
+ request: request
135
+ )
136
+ end
137
+
138
+ # Renders the `operation_results` as a JSON API Document.
139
+ #
140
+ # @param operation_results [JSONAPI::OperationResults] a collection of results from various operations.
141
+ # @param request [JSONAPI::Request] the original request that generated the `operation_results`.
142
+ # @return [Hash] rendered, but not encoded JSON
143
+ def render_results(operation_results:, request:)
144
+ response_document = create_response_document(
145
+ operation_results: operation_results,
146
+ request: request
147
+ )
148
+ response_document.contents.as_json
149
+ end
150
+
151
+ # Class to serialize `JSONAPI::Resource`s to JSON API documents.
152
+ #
153
+ # @return [Class<JSONAPI::ResourceSerializer>]
154
+ def resource_serializer_klass
155
+ @resource_serializer_klass ||= JSONAPI::ResourceSerializer
156
+ end
157
+
158
+ # Control by setting in an initializer:
159
+ # JSONAPI.configuration.route = :camelized_route
160
+ #
161
+ # @return [JSONAPI::RouteFormatter]
162
+ def route_formatter
163
+ JSONAPI.configuration.route_formatter
164
+ end
165
+
166
+ # Options passed to `resource_serializer_klass` instance when serializing the JSON API document.
167
+ #
168
+ # @return [Hash] Defaults to `{}`
169
+ def serialization_options
170
+ {}
171
+ end
172
+ end