workato-connector-sdk 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +20 -0
- data/README.md +1093 -0
- data/exe/workato +5 -0
- data/lib/workato/cli/edit_command.rb +71 -0
- data/lib/workato/cli/exec_command.rb +191 -0
- data/lib/workato/cli/generate_command.rb +105 -0
- data/lib/workato/cli/generators/connector_generator.rb +94 -0
- data/lib/workato/cli/generators/master_key_generator.rb +56 -0
- data/lib/workato/cli/main.rb +142 -0
- data/lib/workato/cli/push_command.rb +208 -0
- data/lib/workato/connector/sdk/account_properties.rb +60 -0
- data/lib/workato/connector/sdk/action.rb +88 -0
- data/lib/workato/connector/sdk/block_invocation_refinements.rb +30 -0
- data/lib/workato/connector/sdk/connector.rb +230 -0
- data/lib/workato/connector/sdk/dsl/account_property.rb +15 -0
- data/lib/workato/connector/sdk/dsl/call.rb +17 -0
- data/lib/workato/connector/sdk/dsl/error.rb +15 -0
- data/lib/workato/connector/sdk/dsl/http.rb +60 -0
- data/lib/workato/connector/sdk/dsl/lookup_table.rb +15 -0
- data/lib/workato/connector/sdk/dsl/time.rb +21 -0
- data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +105 -0
- data/lib/workato/connector/sdk/dsl/workato_schema.rb +15 -0
- data/lib/workato/connector/sdk/dsl.rb +46 -0
- data/lib/workato/connector/sdk/errors.rb +30 -0
- data/lib/workato/connector/sdk/lookup_tables.rb +62 -0
- data/lib/workato/connector/sdk/object_definitions.rb +74 -0
- data/lib/workato/connector/sdk/operation.rb +217 -0
- data/lib/workato/connector/sdk/request.rb +399 -0
- data/lib/workato/connector/sdk/settings.rb +130 -0
- data/lib/workato/connector/sdk/summarize.rb +61 -0
- data/lib/workato/connector/sdk/trigger.rb +96 -0
- data/lib/workato/connector/sdk/version.rb +9 -0
- data/lib/workato/connector/sdk/workato_schemas.rb +37 -0
- data/lib/workato/connector/sdk/xml.rb +35 -0
- data/lib/workato/connector/sdk.rb +58 -0
- data/lib/workato/extension/array.rb +124 -0
- data/lib/workato/extension/case_sensitive_headers.rb +51 -0
- data/lib/workato/extension/currency.rb +15 -0
- data/lib/workato/extension/date.rb +14 -0
- data/lib/workato/extension/enumerable.rb +55 -0
- data/lib/workato/extension/extra_chain_cert.rb +40 -0
- data/lib/workato/extension/hash.rb +13 -0
- data/lib/workato/extension/integer.rb +17 -0
- data/lib/workato/extension/nil_class.rb +17 -0
- data/lib/workato/extension/object.rb +38 -0
- data/lib/workato/extension/phone.rb +14 -0
- data/lib/workato/extension/string.rb +268 -0
- data/lib/workato/extension/symbol.rb +13 -0
- data/lib/workato/extension/time.rb +13 -0
- data/lib/workato/testing/vcr_encrypted_cassette_serializer.rb +38 -0
- data/lib/workato/testing/vcr_multipart_body_matcher.rb +32 -0
- data/lib/workato-connector-sdk.rb +3 -0
- data/templates/.rspec.erb +3 -0
- data/templates/Gemfile.erb +10 -0
- data/templates/connector.rb.erb +37 -0
- data/templates/spec/action_spec.rb.erb +36 -0
- data/templates/spec/connector_spec.rb.erb +18 -0
- data/templates/spec/method_spec.rb.erb +13 -0
- data/templates/spec/object_definition_spec.rb.erb +18 -0
- data/templates/spec/pick_list_spec.rb.erb +13 -0
- data/templates/spec/spec_helper.rb.erb +38 -0
- data/templates/spec/trigger_spec.rb.erb +61 -0
- metadata +372 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Workato
|
4
|
+
module Connector
|
5
|
+
module Sdk
|
6
|
+
class Summarize
|
7
|
+
ARRAY_SUMMARIZATION_LIMIT = 100
|
8
|
+
STRING_SUMMARIZATION_LIMIT = 1024
|
9
|
+
|
10
|
+
def initialize(data:, paths:)
|
11
|
+
@paths = paths
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
return data if paths.blank?
|
17
|
+
|
18
|
+
summarized = data
|
19
|
+
paths.each do |path|
|
20
|
+
steps = path.split('.')
|
21
|
+
if above_summarization_limit?(summarized.dig(*steps))
|
22
|
+
summarized = data.deep_dup if summarized.equal?(data)
|
23
|
+
apply_summarization_limit(summarized, steps)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
summarized
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :data,
|
33
|
+
:paths
|
34
|
+
|
35
|
+
def above_summarization_limit?(candidate)
|
36
|
+
candidate.is_a?(::Array) && candidate.length > ARRAY_SUMMARIZATION_LIMIT ||
|
37
|
+
candidate.is_a?(::String) && candidate.length > STRING_SUMMARIZATION_LIMIT
|
38
|
+
end
|
39
|
+
|
40
|
+
def apply_summarization_limit(summarized, steps)
|
41
|
+
container = if steps.length > 1
|
42
|
+
summarized.dig(*steps[0..-2])
|
43
|
+
else
|
44
|
+
summarized
|
45
|
+
end
|
46
|
+
candidate = container[steps.last]
|
47
|
+
case candidate
|
48
|
+
when Array
|
49
|
+
candidate[(ARRAY_SUMMARIZATION_LIMIT - 2)..-2] =
|
50
|
+
"... #{candidate.length - ARRAY_SUMMARIZATION_LIMIT} items ..."
|
51
|
+
when String
|
52
|
+
candidate[(STRING_SUMMARIZATION_LIMIT - 1)..-1] =
|
53
|
+
"... #{candidate.length - STRING_SUMMARIZATION_LIMIT} characters ..."
|
54
|
+
else
|
55
|
+
candidate
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Workato
|
6
|
+
module Connector
|
7
|
+
module Sdk
|
8
|
+
class Trigger < Operation
|
9
|
+
using BlockInvocationRefinements
|
10
|
+
|
11
|
+
def initialize(trigger:, connection: {}, methods: {}, settings: {}, object_definitions: nil)
|
12
|
+
super(
|
13
|
+
operation: trigger,
|
14
|
+
connection: connection,
|
15
|
+
methods: methods,
|
16
|
+
settings: settings,
|
17
|
+
object_definitions: object_definitions
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def poll_page(settings = nil, input = {}, closure = nil, extended_input_schema = [],
|
22
|
+
extended_output_schema = [])
|
23
|
+
poll_proc = trigger[:poll]
|
24
|
+
output = execute(
|
25
|
+
settings,
|
26
|
+
{ input: input, closure: closure },
|
27
|
+
extended_input_schema,
|
28
|
+
extended_output_schema
|
29
|
+
) do |connection, payload, eis, eos|
|
30
|
+
instance_exec(connection, payload[:input], payload[:closure], eis, eos, &poll_proc)
|
31
|
+
end
|
32
|
+
output[:events] = ::Array.wrap(output[:events]).reverse!.uniq(&trigger[:dedup])
|
33
|
+
output[:next_poll] = output[:next_poll].presence || closure
|
34
|
+
output
|
35
|
+
end
|
36
|
+
|
37
|
+
def poll(settings = nil, input = {}, closure = nil, extended_input_schema = [], extended_output_schema = [])
|
38
|
+
events = []
|
39
|
+
|
40
|
+
loop do
|
41
|
+
output = poll_page(settings, input, closure, extended_input_schema, extended_output_schema)
|
42
|
+
events = output[:events] + events
|
43
|
+
closure = output[:next_poll]
|
44
|
+
|
45
|
+
break unless output[:can_poll_more]
|
46
|
+
end
|
47
|
+
|
48
|
+
{
|
49
|
+
events: events.uniq(&trigger[:dedup]),
|
50
|
+
can_poll_more: false,
|
51
|
+
next_poll: closure
|
52
|
+
}.with_indifferent_access
|
53
|
+
end
|
54
|
+
|
55
|
+
def dedup(input = {})
|
56
|
+
trigger[:dedup].call(input)
|
57
|
+
end
|
58
|
+
|
59
|
+
def webhook_notification(input = {}, payload = {}, extended_input_schema = [],
|
60
|
+
extended_output_schema = [], headers = {}, params = {})
|
61
|
+
Dsl::WithDsl.execute(
|
62
|
+
input.with_indifferent_access,
|
63
|
+
payload.with_indifferent_access,
|
64
|
+
extended_input_schema.map(&:with_indifferent_access),
|
65
|
+
extended_output_schema.map(&:with_indifferent_access),
|
66
|
+
headers.with_indifferent_access,
|
67
|
+
params.with_indifferent_access,
|
68
|
+
&trigger[:webhook_notification]
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def webhook_subscribe(webhook_url = '', settings = nil, input = {}, recipe_id = SecureRandom.uuid)
|
73
|
+
webhook_subscribe_proc = trigger[:webhook_subscribe]
|
74
|
+
execute(settings, { input: input, webhook_url: webhook_url, recipe_id: recipe_id }) do |connection, payload|
|
75
|
+
instance_exec(
|
76
|
+
payload[:webhook_url],
|
77
|
+
connection,
|
78
|
+
payload[:input],
|
79
|
+
payload[:recipe_id],
|
80
|
+
&webhook_subscribe_proc
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def webhook_unsubscribe(webhook_subscribe_output = {})
|
86
|
+
webhook_unsubscribe_proc = trigger[:webhook_unsubscribe]
|
87
|
+
execute(nil, webhook_subscribe_output) do |_connection, input|
|
88
|
+
instance_exec(input, &webhook_unsubscribe_proc)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
alias trigger operation
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Workato
|
6
|
+
module Connector
|
7
|
+
module Sdk
|
8
|
+
class WorkatoSchemas
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def from_json(path = DEFAULT_SCHEMAS_PATH)
|
13
|
+
load_data(JSON.parse(File.read(path)))
|
14
|
+
end
|
15
|
+
|
16
|
+
delegate :find,
|
17
|
+
:load_data,
|
18
|
+
to: :instance
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_data(data)
|
22
|
+
@schemas_by_id ||= {}.with_indifferent_access
|
23
|
+
@schemas_by_id.merge!(data.stringify_keys)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(id)
|
27
|
+
unless @schemas_by_id
|
28
|
+
raise 'Workato Schemas are not initialized. ' \
|
29
|
+
'Init data by calling WorkatoSchemas.from_json or WorkatoSchemas.load_data'
|
30
|
+
end
|
31
|
+
|
32
|
+
@schemas_by_id.fetch(id.to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module Workato
|
6
|
+
module Connector
|
7
|
+
module Sdk
|
8
|
+
module Xml
|
9
|
+
def self.parse_xml_to_hash(payload, strip_namespaces: false)
|
10
|
+
parse_options = Nokogiri::XML::ParseOptions.new.nonet
|
11
|
+
lazy_reader = Nokogiri::XML::Reader(payload, nil, nil, parse_options).to_enum.lazy
|
12
|
+
lazy_reader.each_with_object([{}]) do |node, ancestors|
|
13
|
+
ancestors.shift while ancestors.count > node.depth + 1
|
14
|
+
case node.node_type
|
15
|
+
when Nokogiri::XML::Reader::TYPE_ELEMENT
|
16
|
+
element = HashWithIndifferentAccess.new
|
17
|
+
node.attributes&.each do |name, value|
|
18
|
+
element["@#{strip_namespaces ? name[/(?:^xmlns:)?[^:]+$/] : name}"] = value
|
19
|
+
end
|
20
|
+
(ancestors.first[strip_namespaces ? node.name[/[^:]+$/] : node.name] ||= []).push(element)
|
21
|
+
ancestors.unshift(element)
|
22
|
+
when Nokogiri::XML::Reader::TYPE_TEXT, Nokogiri::XML::Reader::TYPE_CDATA
|
23
|
+
element = ancestors.first
|
24
|
+
if element.key?(:content!)
|
25
|
+
element[:content!] += node.value
|
26
|
+
else
|
27
|
+
element[:content!] = node.value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end.last
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Workato
|
4
|
+
module Connector
|
5
|
+
module Sdk
|
6
|
+
DEFAULT_MASTER_KEY_ENV = 'WORKATO_CONNECTOR_MASTER_KEY'
|
7
|
+
DEFAULT_MASTER_KEY_PATH = 'master.key'
|
8
|
+
|
9
|
+
DEFAULT_CONNECTOR_PATH = 'connector.rb'
|
10
|
+
|
11
|
+
DEFAULT_SETTINGS_PATH = 'settings.yaml'
|
12
|
+
DEFAULT_ENCRYPTED_SETTINGS_PATH = 'settings.yaml.enc'
|
13
|
+
|
14
|
+
DEFAULT_ACCOUNT_PROPERTIES_PATH = 'account_properties.yaml'
|
15
|
+
DEFAULT_ENCRYPTED_ACCOUNT_PROPERTIES_PATH = 'account_properties.yaml.enc'
|
16
|
+
|
17
|
+
DEFAULT_LOOKUP_TABLES_PATH = 'lookup_tables.yaml'
|
18
|
+
|
19
|
+
DEFAULT_TIME_ZONE = 'Pacific Time (US & Canada)'
|
20
|
+
|
21
|
+
DEFAULT_SCHEMAS_PATH = 'workato_schemas.json'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Global libs and monkey patches
|
27
|
+
require 'active_support/all'
|
28
|
+
require 'active_support/json'
|
29
|
+
require_relative '../extension/array'
|
30
|
+
require_relative '../extension/case_sensitive_headers'
|
31
|
+
require_relative '../extension/currency'
|
32
|
+
require_relative '../extension/date'
|
33
|
+
require_relative '../extension/enumerable'
|
34
|
+
require_relative '../extension/extra_chain_cert'
|
35
|
+
require_relative '../extension/hash'
|
36
|
+
require_relative '../extension/integer'
|
37
|
+
require_relative '../extension/nil_class'
|
38
|
+
require_relative '../extension/object'
|
39
|
+
require_relative '../extension/phone'
|
40
|
+
require_relative '../extension/string'
|
41
|
+
require_relative '../extension/symbol'
|
42
|
+
require_relative '../extension/time'
|
43
|
+
|
44
|
+
require_relative './sdk/account_properties'
|
45
|
+
require_relative './sdk/action'
|
46
|
+
require_relative './sdk/connector'
|
47
|
+
require_relative './sdk/dsl'
|
48
|
+
require_relative './sdk/errors'
|
49
|
+
require_relative './sdk/lookup_tables'
|
50
|
+
require_relative './sdk/object_definitions'
|
51
|
+
require_relative './sdk/operation'
|
52
|
+
require_relative './sdk/request'
|
53
|
+
require_relative './sdk/settings'
|
54
|
+
require_relative './sdk/summarize'
|
55
|
+
require_relative './sdk/trigger'
|
56
|
+
require_relative './sdk/version'
|
57
|
+
require_relative './sdk/workato_schemas'
|
58
|
+
require_relative './sdk/xml'
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Workato
|
4
|
+
module Extension
|
5
|
+
module Array
|
6
|
+
def to_csv(options = {})
|
7
|
+
options ||= {}
|
8
|
+
multi_line = options[:multi_line]
|
9
|
+
multi_line = true if multi_line.nil?
|
10
|
+
options = options.dup
|
11
|
+
|
12
|
+
if multi_line && first.is_a?(::Array)
|
13
|
+
options[:multi_line] = false
|
14
|
+
map { |r| r.to_csv(options) }.join
|
15
|
+
else
|
16
|
+
options.delete(:multi_line)
|
17
|
+
super(options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ignored(*names)
|
22
|
+
reject { |field| names.include?(field[:name]) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def only(*names)
|
26
|
+
select { |field| names.include?(field[:name]) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def required(*names)
|
30
|
+
map { |field| names.include?(field[:name]) ? field.merge(optional: false) : field }
|
31
|
+
end
|
32
|
+
|
33
|
+
class ArrayWhere < SimpleDelegator
|
34
|
+
def initialize(list, options = {})
|
35
|
+
@_list = list
|
36
|
+
@_operations = []
|
37
|
+
@_resolved = false
|
38
|
+
where(options || {})
|
39
|
+
end
|
40
|
+
|
41
|
+
def where(options = {})
|
42
|
+
@_operations << [options] if options.present?
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def not(options = {})
|
47
|
+
if options.present?
|
48
|
+
where(options)
|
49
|
+
@_operations.last << true
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.parse_operation(key)
|
55
|
+
operation = key.to_s.match(/ *[><=!]={0,1} *\Z/)
|
56
|
+
stripped_key, operation = if operation.present?
|
57
|
+
[operation.pre_match, operation.to_s.strip]
|
58
|
+
else
|
59
|
+
[key.to_s, nil]
|
60
|
+
end
|
61
|
+
keys = stripped_key.split_strip_compact('.')
|
62
|
+
keys = keys.map(&:to_sym) if key.is_a?(::Symbol)
|
63
|
+
[keys, operation]
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.coerce_operands(lhs_name, lhs, rhs, operation)
|
67
|
+
if operation == 'include?'
|
68
|
+
lhs, rhs = rhs, lhs
|
69
|
+
end
|
70
|
+
|
71
|
+
if operation.match?(/[><]/) || %w[match? include?].include?(operation)
|
72
|
+
raise "The '#{lhs_name}' is nil" if lhs.nil?
|
73
|
+
raise "Can't compare '#{lhs_name}' with nil" if rhs.nil?
|
74
|
+
end
|
75
|
+
|
76
|
+
if lhs.is_a?(::Numeric) && rhs.try(:is_number?)
|
77
|
+
rhs = rhs.is_int? ? rhs.to_i : rhs.to_f
|
78
|
+
elsif rhs.is_a?(::Numeric) && lhs.try(:is_number?)
|
79
|
+
lhs = lhs.is_int? ? lhs.to_i : lhs.to_f
|
80
|
+
end
|
81
|
+
[lhs, rhs]
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.nested_attr(item, keys)
|
85
|
+
keys.reduce(item) do |obj, key|
|
86
|
+
obj[key] unless obj.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.match?(operations, item)
|
91
|
+
operations.all? do |args, negate|
|
92
|
+
result = args.all? do |key, value|
|
93
|
+
keys, operation = parse_operation(key)
|
94
|
+
compared = nested_attr(item, keys)
|
95
|
+
if operation.blank?
|
96
|
+
operation = case value
|
97
|
+
when ::Array, Range
|
98
|
+
'include?'
|
99
|
+
when Regexp
|
100
|
+
'match?'
|
101
|
+
else
|
102
|
+
'=='
|
103
|
+
end
|
104
|
+
end
|
105
|
+
lhs, rhs = coerce_operands(keys.join('.'), compared, value, operation)
|
106
|
+
lhs.send(operation, rhs)
|
107
|
+
end
|
108
|
+
negate ? !result : result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def __getobj__
|
113
|
+
return super if @_resolved
|
114
|
+
|
115
|
+
filtered = @_list.select { |item| self.class.match?(@_operations, item) }
|
116
|
+
@_resolved = true
|
117
|
+
__setobj__(filtered)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
Array.prepend(Workato::Extension::Array)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-client'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Workato
|
7
|
+
module Extension
|
8
|
+
module CaseSensitiveHeaders
|
9
|
+
module Net
|
10
|
+
module HTTPHeader
|
11
|
+
attr_accessor :case_sensitive_headers
|
12
|
+
|
13
|
+
def capitalize(modified_name)
|
14
|
+
return super if case_sensitive_headers.blank?
|
15
|
+
|
16
|
+
original_name = case_sensitive_headers.keys.find { |name| name.downcase == modified_name }
|
17
|
+
original_name.presence || super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
::Net::HTTPHeader.prepend Net::HTTPHeader
|
23
|
+
::Net::HTTPGenericRequest.prepend Net::HTTPHeader
|
24
|
+
|
25
|
+
module RestClient
|
26
|
+
module Request
|
27
|
+
attr_accessor :case_sensitive_headers
|
28
|
+
|
29
|
+
def processed_headers
|
30
|
+
return @processed_headers if case_sensitive_headers.blank?
|
31
|
+
return case_sensitive_headers if @processed_headers.blank?
|
32
|
+
|
33
|
+
@processed_headers.merge(case_sensitive_headers)
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(&block)
|
37
|
+
# With 2.0.0+, net/http accepts URI objects in requests and handles wrapping
|
38
|
+
# IPv6 addresses in [] for use in the Host request header.
|
39
|
+
net_http_request = net_http_request_class(method).new(uri, processed_headers)
|
40
|
+
net_http_request.case_sensitive_headers = case_sensitive_headers
|
41
|
+
transmit(uri, net_http_request, payload, &block)
|
42
|
+
ensure
|
43
|
+
payload&.close
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
::RestClient::Request.prepend RestClient::Request
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|