simplyq 0.8.0rc

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,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "time"
5
+
6
+ module Simplyq
7
+ module API
8
+ class EventAPI
9
+ attr_reader :client
10
+
11
+ API_PATH = "/v1/application/{app_id}/event"
12
+ API_RETRIEVE_PATH = "/v1/application/{app_id}/event/{event_id}"
13
+ API_DELIVERY_ATTEMPTS_PATH = "/v1/application/{app_id}/event/{event_id}/delivery_attempt"
14
+ API_DELIVERY_ATTEMPT_PATH = "/v1/application/{app_id}/event/{event_id}/delivery_attempt/{delivery_attempt_id}/"
15
+ API_ENDPOINTS_PATH = "/v1/application/{app_id}/event/{event_id}/endpoint"
16
+ API_RETRY_PATH = "/v1/application/{app_id}/endpoint/{endpoint_id}/event/{event_id}"
17
+
18
+ # Initializes a new API object.
19
+ #
20
+ # @param client [Simplyq::Client] the client object that will be used to
21
+ # make HTTP requests.
22
+ def initialize(client)
23
+ @client = client
24
+ end
25
+
26
+ def retrieve(application_id, event_id)
27
+ path = API_RETRIEVE_PATH.gsub("{app_id}", application_id.to_s).gsub("{event_id}", event_id.to_s)
28
+
29
+ data, status, headers = client.call_api(:get, path)
30
+ decerialize(data)
31
+ end
32
+
33
+ def list(application_id, params = {})
34
+ path = API_PATH.gsub("{app_id}", application_id.to_s)
35
+
36
+ data, status, headers = client.call_api(:get, path, { query_params: params })
37
+ decerialize_list(data, params: params, list_args: [application_id])
38
+ end
39
+
40
+ def create(application_id, event)
41
+ path = API_PATH.gsub("{app_id}", application_id.to_s)
42
+
43
+ data, status, headers = client.call_api(:post, path, { body: build_model(event).to_h })
44
+ decerialize(data)
45
+ end
46
+
47
+ def retrieve_delivery_attempts(application_id, event_id, params = {})
48
+ path = API_DELIVERY_ATTEMPTS_PATH.gsub("{app_id}", application_id.to_s).gsub("{event_id}", event_id.to_s)
49
+
50
+ data, status, headers = client.call_api(:get, path, { query_params: params })
51
+ decerialize_delivery_attempts_list(data, params: params, list_args: [application_id, event_id])
52
+ end
53
+
54
+ def retrieve_endpoints(application_id, event_id, params = {})
55
+ path = API_ENDPOINTS_PATH.gsub("{app_id}", application_id.to_s).gsub("{event_id}", event_id.to_s)
56
+
57
+ data, status, headers = client.call_api(:get, path, { query_params: params })
58
+ decerialize_endpoints_list(data, params: params, list_args: [application_id, event_id])
59
+ end
60
+
61
+ def retry(application_id, endpoint_id, event_id)
62
+ path = API_RETRY_PATH.gsub("{app_id}", application_id.to_s)
63
+ .gsub("{endpoint_id}", endpoint_id.to_s)
64
+ .gsub("{event_id}", event_id.to_s)
65
+
66
+ data, status, headers = client.call_api(:post, path)
67
+ status == 202
68
+ end
69
+
70
+ def retrieve_delivery_attempt(application_id, event_id, delivery_attempt_id)
71
+ path = API_DELIVERY_ATTEMPT_PATH.gsub("{app_id}", application_id.to_s)
72
+ .gsub("{event_id}", event_id.to_s)
73
+ .gsub("{delivery_attempt_id}", delivery_attempt_id.to_s)
74
+
75
+ data, status, headers = client.call_api(:get, path)
76
+ decerialize_delivery_attempt(data)
77
+ end
78
+
79
+ private def build_model(data)
80
+ return data if data.is_a?(Simplyq::Model::Event)
81
+ raise ArgumentError, "Invalid data must be a Simplyq::Model::Event or Hash" unless data.is_a?(Hash)
82
+
83
+ Simplyq::Model::Event.from_hash(data)
84
+ end
85
+
86
+ private def decerialize(json_data)
87
+ data = body_to_json(json_data)
88
+
89
+ Simplyq::Model::Event.from_hash(data)
90
+ end
91
+
92
+ private def decerialize_delivery_attempt(json_data)
93
+ data = body_to_json(json_data)
94
+
95
+ Simplyq::Model::DeliveryAttempt.from_hash(data)
96
+ end
97
+
98
+ private def decerialize_list(json_data, params: {}, list_args: [])
99
+ data = body_to_json(json_data)
100
+
101
+ Simplyq::Model::List.new(
102
+ Simplyq::Model::Event, data,
103
+ api_method: :list,
104
+ list_args: list_args,
105
+ filters: params, api: self
106
+ )
107
+ end
108
+
109
+ private def decerialize_delivery_attempts_list(json_data, params: {}, list_args: [])
110
+ data = body_to_json(json_data)
111
+
112
+ Simplyq::Model::List.new(
113
+ Simplyq::Model::DeliveryAttempt, data,
114
+ api_method: :retrieve_delivery_attempts,
115
+ list_args: list_args,
116
+ filters: params, api: self
117
+ )
118
+ end
119
+
120
+ private def decerialize_endpoints_list(json_data, params: {}, list_args: [])
121
+ data = body_to_json(json_data)
122
+
123
+ Simplyq::Model::List.new(
124
+ Simplyq::Model::Endpoint, data,
125
+ api_method: :retrieve_endpoints,
126
+ list_args: list_args,
127
+ filters: params, api: self
128
+ )
129
+ end
130
+
131
+ private def body_to_json(body)
132
+ return if body.nil?
133
+
134
+ JSON.parse(body, symbolize_names: true)
135
+ rescue JSON::ParserError
136
+ raise Simplyq::APIError.new("Invalid JSON in response body.", http_body: body)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module Simplyq
7
+ class Client
8
+ HEADER_AUTHORIZATION = "Authorization"
9
+
10
+ USER_AGENT = "simplyq-ruby/#{Simplyq::VERSION}"
11
+
12
+ attr_reader :config
13
+
14
+ # Initialize client to connect to SimplyQ API
15
+ #
16
+ # @param config_options [Simplyq::Configuration|Hash] a configuration object or a hash of configuration options
17
+ def initialize(config_options = {})
18
+ @config = case config_options
19
+ when Hash
20
+ Simplyq::Configuration.setup do |config|
21
+ config_options.each do |key, value|
22
+ config.send("#{key}=", value)
23
+ end
24
+ end
25
+ when Simplyq::Configuration
26
+ config_options
27
+ else
28
+ raise ArgumentError, "Invalid configuration options #{config_options}"
29
+ end
30
+ @default_headers = {
31
+ "Content-Type" => "application/json",
32
+ "User-Agent" => USER_AGENT
33
+ }
34
+ end
35
+
36
+ def applications
37
+ @applications ||= Simplyq::API::ApplicationAPI.new(self)
38
+ end
39
+
40
+ def endpoints
41
+ @endpoints ||= Simplyq::API::EndpointAPI.new(self)
42
+ end
43
+
44
+ def events
45
+ @events ||= Simplyq::API::EventAPI.new(self)
46
+ end
47
+
48
+ def check_api_key!
49
+ raise AuthenticationError, "No API key provided." unless config.api_key
50
+
51
+ raise AuthenticationError, "Invalid API key as it includes spaces" if config.api_key =~ /\s/
52
+ end
53
+
54
+ ERROR_MESSAGE_CONNECTION =
55
+ "Unexpected error communicating when trying to connect to " \
56
+ "SimplyQ (%s). You may be seeing this message because your DNS is not " \
57
+ "working or you don't have an internet connection. To check, try " \
58
+ "running `host api.simplyq.com` from the command line."
59
+ ERROR_MESSAGE_SSL =
60
+ "Could not establish a secure connection to SimplyQ (%s), you " \
61
+ "may need to upgrade your OpenSSL version. To check, try running " \
62
+ "`openssl s_client -connect api.simplyq.com:443` from the command " \
63
+ "line."
64
+
65
+ ERROR_MESSAGE_TIMEOUT_SUFFIX =
66
+ "Please check your internet connection and try again. " \
67
+ "If this problem persists, you should check SimplyQ's service " \
68
+ "status at https://simplyq.statuspage.io, or let us know at " \
69
+ "support@simplyq.io."
70
+
71
+ ERROR_MESSAGE_TIMEOUT_CONNECT =
72
+ "Timed out connecting to SimplyQ (%s). #{ERROR_MESSAGE_TIMEOUT_SUFFIX}"
73
+
74
+ ERROR_MESSAGE_TIMEOUT_READ =
75
+ "Timed out communicating with SimplyQ (%s). #{ERROR_MESSAGE_TIMEOUT_SUFFIX}"
76
+
77
+ NETWORK_ERROR_MESSAGES_MAP = {
78
+ EOFError => ERROR_MESSAGE_CONNECTION,
79
+ Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION,
80
+ Errno::ECONNRESET => ERROR_MESSAGE_CONNECTION,
81
+ Errno::EHOSTUNREACH => ERROR_MESSAGE_CONNECTION,
82
+ Errno::ETIMEDOUT => ERROR_MESSAGE_TIMEOUT_CONNECT,
83
+ SocketError => ERROR_MESSAGE_CONNECTION,
84
+
85
+ Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
86
+ Net::ReadTimeout => ERROR_MESSAGE_TIMEOUT_READ,
87
+
88
+ Faraday::TimeoutError => ERROR_MESSAGE_TIMEOUT_READ,
89
+ Faraday::ConnectionFailed => ERROR_MESSAGE_CONNECTION,
90
+
91
+ OpenSSL::SSL::SSLError => ERROR_MESSAGE_SSL,
92
+ Faraday::SSLError => ERROR_MESSAGE_SSL
93
+ }.freeze
94
+ private_constant :NETWORK_ERROR_MESSAGES_MAP
95
+
96
+ # Call an API with given options.
97
+ #
98
+ # @return [Array<(Object, Integer, Hash)>] an array of 3 elements:
99
+ # the data deserialized from response body (could be nil), response status code and response headers.
100
+ def call_api(http_method, path, opts = {})
101
+ check_api_key!
102
+
103
+ begin
104
+ response = connection.public_send(http_method.to_sym.downcase) do |req|
105
+ build_request(http_method, path, req, opts)
106
+ end
107
+
108
+ config.logger.debug "HTTP response body ~BEGIN~\n#{response.body}\n~END~\n" if config.debugging
109
+
110
+ unless response.success?
111
+ if response.status.zero?
112
+ # Errors from libcurl will be made visible here
113
+ raise ApiError.new(response.reason_phrase, http_status: 0)
114
+ else
115
+ raise specific_http_error(response, get_http_error_data(response).merge(params: opts[:query_params]))
116
+ end
117
+ end
118
+ rescue *NETWORK_ERROR_MESSAGES_MAP.keys => e
119
+ handle_network_error(e)
120
+ end
121
+
122
+ [response.body, response.status, response.headers]
123
+ end
124
+
125
+ def handle_network_error(error)
126
+ errors, message = NETWORK_ERROR_MESSAGES_MAP.detect do |(e, _)|
127
+ error.is_a?(e)
128
+ end
129
+
130
+ if errors.nil?
131
+ message = "Unexpected error #{error.class.name} communicating " \
132
+ "with SimplyQ. Please let us know at support@simplyq.io."
133
+ end
134
+
135
+ message = message % config.base_url
136
+ message += "\n\n(Network error: #{error.message})"
137
+
138
+ raise APIConnectionError.new(message, http_status: 0, error: error)
139
+ end
140
+
141
+ def get_http_error_data(response)
142
+ body = safe_json_parse_body(response)
143
+ if body.is_a?(Hash)
144
+ message = body[:error] || body[:message]
145
+
146
+ message = "Invalid request" if message.nil? && body[:errors]
147
+
148
+ return {
149
+ message: message,
150
+ errors: body[:errors],
151
+ code: body[:code]
152
+ }
153
+ end
154
+
155
+ { message: response.reason_phrase }
156
+ end
157
+
158
+ def safe_json_parse_body(response)
159
+ return nil if response.body.nil?
160
+
161
+ JSON.parse(response.body, symbolize_names: true)
162
+ rescue JSON::ParserError
163
+ nil
164
+ end
165
+
166
+ def specific_http_error(resp, error_data = {})
167
+ # The standard arguments that are passed to API exceptions
168
+ opts = {
169
+ http_body: resp.body,
170
+ http_headers: resp.headers,
171
+ http_status: resp.status,
172
+ code: error_data[:code]
173
+ }
174
+
175
+ case resp.status
176
+ when 400, 404, 422
177
+ case error_data[:type]
178
+ when "idempotency_error"
179
+ IdempotencyError.new(error_data[:message], **opts)
180
+ else
181
+ InvalidRequestError.new(
182
+ error_data[:message], error_data[:param],
183
+ **opts.merge(errors: error_data[:errors])
184
+ )
185
+ end
186
+ when 401
187
+ AuthenticationError.new(error_data[:message] || resp.reason_phrase, **opts)
188
+ when 402
189
+ PaymentRequiredError.new(error_data[:message] || resp.reason_phrase, **opts)
190
+ when 403
191
+ PermissionError.new(error_data[:message] || resp.reason_phrase, **opts)
192
+ when 429
193
+ RateLimitError.new(error_data[:message] || resp.reason_phrase, **opts)
194
+ else
195
+ APIError.new(error_data[:message] || resp.reason_phrase, **opts)
196
+ end
197
+ end
198
+
199
+ def build_request_url(path)
200
+ # Add leading and trailing slashes to path
201
+ path = "/#{path}".gsub(%r{/+}, "/")
202
+ @config.base_url + path
203
+ end
204
+
205
+ # Builds the HTTP request
206
+ #
207
+ # @param [String] http_method HTTP method/verb (e.g. POST)
208
+ # @param [String] path URL path (e.g. /account/new)
209
+ # @option opts [Hash] :header_params Header parameters
210
+ # @option opts [Hash] :query_params Query parameters
211
+ # @option opts [Hash] :form_params Query parameters
212
+ # @option opts [Object] :body HTTP body (JSON/XML)
213
+ # @return [Faraday::Request] A Faraday Request
214
+ def build_request(http_method, path, request, opts = {})
215
+ url = build_request_url(path)
216
+ http_method = http_method.to_sym.downcase
217
+
218
+ header_params = @default_headers.merge(opts[:header_params] || {})
219
+ query_params = opts[:query_params] || {}
220
+ form_params = opts[:form_params] || {}
221
+
222
+ header_params[HEADER_AUTHORIZATION] = config.auth_api_key
223
+
224
+ if %i[post patch put delete].include?(http_method)
225
+ req_body = build_request_body(header_params, form_params, opts[:body])
226
+ config.logger.debug "HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n" if config.debugging
227
+ end
228
+ request.headers = header_params
229
+ request.body = req_body
230
+
231
+ # Overload default options only if provided
232
+ request.options.timeout = config.timeout if config.timeout
233
+
234
+ request.url url
235
+ request.params = query_params
236
+ download_file(request) if opts[:return_type] == "File" || opts[:return_type] == "Binary"
237
+ request
238
+ end
239
+
240
+ # Builds the HTTP request body
241
+ #
242
+ # @param [Hash] header_params Header parameters
243
+ # @param [Hash] form_params Query parameters
244
+ # @param [Object] body HTTP body (JSON/XML)
245
+ # @return [String] HTTP body data in the form of string
246
+ def build_request_body(header_params, form_params, body)
247
+ # http form
248
+ if header_params["Content-Type"] == "application/x-www-form-urlencoded"
249
+ data = URI.encode_www_form(form_params)
250
+ elsif header_params["Content-Type"] == "multipart/form-data"
251
+ data = {}
252
+ form_params.each do |key, value|
253
+ data[key] = case value
254
+ when ::File, ::Tempfile
255
+ Faraday::FilePart.new(value.path, "application/octet-stream", value.path)
256
+ when ::Array, nil
257
+ # let Faraday handle Array and nil parameters
258
+ value
259
+ else
260
+ value.to_s
261
+ end
262
+ end
263
+ elsif body
264
+ data = body.is_a?(String) ? body : body.to_json
265
+ else
266
+ data = nil
267
+ end
268
+ data
269
+ end
270
+
271
+ def connection
272
+ @connection ||= build_connection
273
+ end
274
+
275
+ def build_connection
276
+ Faraday.new(url: config.base_url, ssl: ssl_options, proxy: config.proxy) do |conn|
277
+ basic_auth(conn)
278
+ config.configure_middleware(conn)
279
+ yield(conn) if block_given?
280
+ conn.adapter(Faraday.default_adapter)
281
+ end
282
+ end
283
+
284
+ def ssl_options
285
+ {
286
+ ca_file: config.ssl_ca_file,
287
+ verify: config.ssl_verify,
288
+ verify_mode: config.ssl_verify_mode,
289
+ client_cert: config.ssl_client_cert,
290
+ client_key: config.ssl_client_key
291
+ }
292
+ end
293
+
294
+ def basic_auth(conn)
295
+ if config.username && config.password
296
+ if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new("2.0")
297
+ conn.request(:authorization, :basic, config.username, config.password)
298
+ else
299
+ conn.request(:basic_auth, config.username, config.password)
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module Simplyq
6
+ class Configuration
7
+ # Defines API keys used with API Key authentications.
8
+ attr_accessor :api_key
9
+
10
+ # Defines the api version
11
+ attr_accessor :api_version
12
+
13
+ # Defines the logger used for debugging.
14
+ # Default to `Rails.logger` (when in Rails) or logging to STDOUT.
15
+ #
16
+ # @return [#debug]
17
+ attr_accessor :logger
18
+
19
+ # Defines the username used with HTTP basic authentication.
20
+ #
21
+ # @return [String]
22
+ attr_accessor :username
23
+
24
+ # Defines the password used with HTTP basic authentication.
25
+ #
26
+ # @return [String]
27
+ attr_accessor :password
28
+
29
+ # Set this to false to skip client side validation in the operation.
30
+ # Default to true.
31
+ # @return [true, false]
32
+ attr_accessor :client_side_validation
33
+
34
+ ### TLS/SSL setting
35
+ # Set this to false to skip verifying SSL certificate when calling API from https server.
36
+ # Default to true.
37
+ #
38
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
39
+ #
40
+ # @return [true, false]
41
+ attr_accessor :ssl_verify
42
+
43
+ ### TLS/SSL setting
44
+ # Any `OpenSSL::SSL::` constant (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL.html)
45
+ #
46
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
47
+ #
48
+ attr_accessor :ssl_verify_mode
49
+
50
+ ### TLS/SSL setting
51
+ # Set this to customize the certificate file to verify the peer.
52
+ #
53
+ # @return [String] the path to the certificate file
54
+ attr_accessor :ssl_ca_file
55
+
56
+ ### TLS/SSL setting
57
+ # Client certificate file (for client certificate)
58
+ attr_accessor :ssl_client_cert
59
+
60
+ ### TLS/SSL setting
61
+ # Client private key file (for client certificate)
62
+ attr_accessor :ssl_client_key
63
+
64
+ ### Proxy setting
65
+ # HTTP Proxy settings
66
+ attr_accessor :proxy
67
+
68
+ attr_accessor :timeout
69
+
70
+ attr_accessor :base_url
71
+
72
+ attr_accessor :debugging
73
+
74
+ attr_reader :open_timeout
75
+ attr_reader :read_timeout
76
+ attr_reader :write_timeout
77
+
78
+ def self.setup
79
+ new.tap do |instance|
80
+ yield(instance) if block_given?
81
+ end
82
+ end
83
+
84
+ def initialize
85
+ @timeout = 30
86
+ @open_timeout = 30
87
+ @read_timeout = 80
88
+ @write_timeout = 30
89
+
90
+ @client_side_validation = true
91
+
92
+ @base_url = "https://api.simplyq.io"
93
+ @middlewares = Hash.new { |h, k| h[k] = [] }
94
+ @logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
95
+
96
+ yield(self) if block_given?
97
+ end
98
+
99
+ # The default Configuration object.
100
+ def self.default
101
+ Configuration.new
102
+ end
103
+
104
+ # Gets Basic Auth token string
105
+ def basic_auth_token
106
+ "Basic #{["#{username}:#{password}"].pack("m").delete("\r\n")}"
107
+ end
108
+
109
+ def auth_api_key
110
+ "Bearer #{@api_key}"
111
+ end
112
+
113
+ # TODO: Remove
114
+ # def base_path=(base_path)
115
+ # # Add leading and trailing slashes to base_path
116
+ # @base_path = "/#{base_path}".gsub(%r{/+}, "/")
117
+ # @base_path = "" if @base_path == "/"
118
+ # end
119
+
120
+ # TODO: Remove
121
+ # Returns base URL for specified operation based on server settings
122
+ # def base_url(operation = nil)
123
+ # index = server_operation_index.fetch(operation, server_index)
124
+ # return "#{scheme}://#{[host, base_path].join("/").gsub(%r{/+}, "/")}".sub(%r{/+\z}, "") if index.nil?
125
+
126
+ # server_url(index, server_operation_variables.fetch(operation, server_variables),
127
+ # operation_server_settings[operation])
128
+ # end
129
+
130
+ # Adds middleware to the stack
131
+ def use(*middleware)
132
+ set_faraday_middleware(:use, *middleware)
133
+ end
134
+
135
+ # Adds request middleware to the stack
136
+ def request(*middleware)
137
+ set_faraday_middleware(:request, *middleware)
138
+ end
139
+
140
+ # Adds response middleware to the stack
141
+ def response(*middleware)
142
+ set_faraday_middleware(:response, *middleware)
143
+ end
144
+
145
+ # Adds Faraday middleware setting information to the stack
146
+ #
147
+ # @example Use the `set_faraday_middleware` method to set middleware information
148
+ # config.set_faraday_middleware(:request, :retry, max: 3, methods: [:get, :post], retry_statuses: [503])
149
+ # config.set_faraday_middleware(:response, :logger, nil, { bodies: true, log_level: :debug })
150
+ # config.set_faraday_middleware(:use, Faraday::HttpCache, store: Rails.cache, shared_cache: false)
151
+ # config.set_faraday_middleware(:insert, 0, FaradayMiddleware::FollowRedirects, { standards_compliant: true, limit: 1 })
152
+ # config.set_faraday_middleware(:swap, 0, Faraday::Response::Logger)
153
+ # config.set_faraday_middleware(:delete, Faraday::Multipart::Middleware)
154
+ #
155
+ # @see https://github.com/lostisland/faraday/blob/v2.3.0/lib/faraday/rack_builder.rb#L92-L143
156
+ def set_faraday_middleware(operation, key, *args, &block)
157
+ unless %i[request response use insert insert_before insert_after swap delete].include?(operation)
158
+ raise ArgumentError, "Invalid faraday middleware operation #{operation}. Must be" \
159
+ " :request, :response, :use, :insert, :insert_before, :insert_after, :swap or :delete."
160
+ end
161
+
162
+ @middlewares[operation] << [key, args, block]
163
+ end
164
+ ruby2_keywords(:set_faraday_middleware) if respond_to?(:ruby2_keywords, true)
165
+
166
+ # Set up middleware on the connection
167
+ def configure_middleware(connection)
168
+ return if @middlewares.empty?
169
+
170
+ %i[request response use insert insert_before insert_after swap].each do |operation|
171
+ next unless @middlewares.key?(operation)
172
+
173
+ @middlewares[operation].each do |key, args, block|
174
+ connection.builder.send(operation, key, *args, &block)
175
+ end
176
+ end
177
+
178
+ if @middlewares.key?(:delete)
179
+ @middlewares[:delete].each do |key, _args, _block|
180
+ connection.builder.delete(key)
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end