workato-connector-sdk 1.0.2 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f52b011085690e264b4e2082fb2f13c1375e74fe
4
- data.tar.gz: 190c0bc7e6264ab9524acdda484e07154dd0b2b8
2
+ SHA256:
3
+ metadata.gz: 053e48199f2b1406ef47a84fb43d7b7d0cc74d74ce0f1f1b650c01fc422bc683
4
+ data.tar.gz: e2155ca61c45230234b105ff0f4ab033205c86b49b35838d88e8a8e473a957b3
5
5
  SHA512:
6
- metadata.gz: 29480d50f931dec0b0f18bf8f7c97f0a6029dd2656ba367ce0a3015a2a32e961bca9981083e01455d9c65cba2244dea5491b34d02e18a9cb8c4ac1a02f9a1d85
7
- data.tar.gz: 1681f2fc8fc0eec6949361ae49ae26fdea3372eab98fb745e9d4a55d192b0e74a34d446655d2b5b08ede89d09cf748d4610ef86b08bab3210165de961004bbb6
6
+ metadata.gz: 4d34393bda1fc5eef5e138c7e742a2a05a7c1ced8fb5a4a8969a1770473c4534cf015c7096cf17a18eb69c669301ca8761ff51a88b3ee8a04ad4550467a23588
7
+ data.tar.gz: b293f65e21f45ef2bab573d687cd12cd77981a7375bec619ce45561ead9e96f895f633e65910dad7226213daacb38b95453c890b96e7b7a957b5085e5055ff94
data/README.md CHANGED
@@ -248,8 +248,9 @@ Usage:
248
248
  workato edit <PATH>
249
249
 
250
250
  Options:
251
- -k, [--key=KEY] # Path to file with encrypt/decrypt key. NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
252
- [--verbose], [--no-verbose]
251
+ -k, [--key=KEY] # Path to file with encrypt/decrypt key.
252
+ # NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
253
+ [--verbose], [--no-verbose]
253
254
 
254
255
  Edit encrypted file, e.g. settings.yaml.enc
255
256
  ```
@@ -261,6 +262,7 @@ Edit encrypted file, e.g. settings.yaml.enc
261
262
  ### 3.3 workato exec
262
263
  ```
263
264
  workato help exec
265
+
264
266
  Usage:
265
267
  workato exec <PATH>
266
268
 
@@ -268,7 +270,8 @@ Options:
268
270
  -c, [--connector=CONNECTOR] # Path to connector source code
269
271
  -s, [--settings=SETTINGS] # Path to plain or encrypted file with connection configs, passwords, tokens, secrets etc
270
272
  -n, [--connection=CONNECTION] # Connection name if settings file contains multiple settings
271
- -k, [--key=KEY] # Path to file with encrypt/decrypt key. NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
273
+ -k, [--key=KEY] # Path to file with encrypt/decrypt key.
274
+ # NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
272
275
  -i, [--input=INPUT] # Path to file with input JSON
273
276
  [--closure=CLOSURE] # Path to file with next poll closure JSON
274
277
  [--continue=CONTINUE] # Path to file with next multistep action continue closure JSON
@@ -286,7 +289,7 @@ Options:
286
289
  [--redirect-url=REDIRECT_URL] # OAuth2 callback url
287
290
  [--refresh-token=REFRESH_TOKEN] # OAuth2 refresh token
288
291
  [--debug], [--no-debug]
289
- [--verbose], [--no-verbose]
292
+ [--verbose], [--no-verbose]
290
293
 
291
294
  Description:
292
295
  The 'workato exec' executes connector's lambda block at <PATH>. Lambda's parameters can be provided if needed, see options part.
@@ -394,13 +397,14 @@ Options:
394
397
  -c, [--connector=CONNECTOR] # Path to connector source code
395
398
  -s, [--settings=SETTINGS] # Path to plain or encrypted file with connection configs, passwords, tokens, secrets etc
396
399
  -n, [--connection=CONNECTION] # Connection name if settings file contains multiple settings
397
- -k, [--key=KEY] # Path to file with encrypt/decrypt key. NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
400
+ -k, [--key=KEY] # Path to file with encrypt/decrypt key.
401
+ # NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
398
402
  [--port=PORT] # Listen requests on specific port
399
403
  # Default: 45555
400
404
  [--ip=IP] # Listen requests on specific interface
401
405
  # Default: 127.0.0.1
402
406
  [--https], [--no-https] # Start HTTPS server using self-signed certificate
403
- [--verbose], [--no-verbose]
407
+ [--verbose], [--no-verbose]
404
408
 
405
409
  Implements OAuth Authorization Code flow
406
410
  ```
@@ -420,11 +424,13 @@ Options:
420
424
  -l, [--logo=LOGO] # Path to connector logo: png or jpeg file
421
425
  -n, [--notes=NOTES] # Release notes
422
426
  -c, [--connector=CONNECTOR] # Path to connector source code
423
- [--api-email=API_EMAIL] # Email for accessing Workato API or set WORKATO_API_EMAIL environment variable
424
- [--api-token=API_TOKEN] # Token for accessing Workato API or set WORKATO_API_TOKEN environment variable
425
- [--environment=ENVIRONMENT] # Server to push connector code to
426
- # Default: live
427
- # Possible values: preview, preview-eu, live, live-eu
427
+ [--api-email=API_EMAIL] # Email for accessing Workato API.
428
+ # If present overrides value from WORKATO_API_EMAIL environment variable.
429
+ [--api-token=API_TOKEN] # Token for accessing Workato API.
430
+ # If present overrides value from WORKATO_API_TOKEN environment variable.
431
+ [--environment=ENVIRONMENT] # Data center specific URL to push connector code.
432
+ # If present overrides value from WORKATO_BASE_URL environment variable.
433
+ # Examples: 'https://app.workato.com', 'https://app.eu.workato.com'
428
434
  [--folder=FOLDER] # Folder ID if you what to push to folder other than Home
429
435
  [--verbose], [--no-verbose]
430
436
 
@@ -89,7 +89,10 @@ module Workato
89
89
  $stdout.pause if verbose?
90
90
  say('')
91
91
  say(message)
92
+
92
93
  new_settings = refresher.call
94
+ break unless new_settings
95
+
93
96
  loop do
94
97
  answer = ask('Updated settings file with new connection attributes? (Yes or No)').to_s.downcase
95
98
  break new_settings if %w[n no].include?(answer)
@@ -41,8 +41,8 @@ module Workato
41
41
  desc: 'Connection name if settings file contains multiple settings'
42
42
  method_option :key, type: :string, aliases: '-k',
43
43
  lazy_default: Workato::Connector::Sdk::DEFAULT_MASTER_KEY_PATH,
44
- desc: 'Path to file with encrypt/decrypt key. '\
45
- "NOTE: key from #{Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV} has higher priority"
44
+ desc: "Path to file with encrypt/decrypt key.\n"\
45
+ "NOTE: key from #{Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV} has higher priority"
46
46
  method_option :input, type: :string, aliases: '-i', desc: 'Path to file with input JSON'
47
47
  method_option :closure, type: :string, desc: 'Path to file with next poll closure JSON'
48
48
  method_option :continue, type: :string, desc: 'Path to file with next multistep action continue closure JSON'
@@ -75,7 +75,7 @@ module Workato
75
75
 
76
76
  method_option :key, type: :string, aliases: '-k',
77
77
  lazy_default: Workato::Connector::Sdk::DEFAULT_MASTER_KEY_PATH,
78
- desc: 'Path to file with encrypt/decrypt key. '\
78
+ desc: "Path to file with encrypt/decrypt key.\n"\
79
79
  "NOTE: key from #{Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV} has higher priority"
80
80
 
81
81
  def edit(path)
@@ -127,17 +127,20 @@ module Workato
127
127
  lazy_default: Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH
128
128
  method_option :api_email,
129
129
  type: :string,
130
- desc: 'Email for accessing Workato API or '\
131
- "set #{Workato::Connector::Sdk::WORKATO_API_EMAIL_ENV} environment variable"
130
+ desc: "Email for accessing Workato API.\n"\
131
+ "If present overrides value from #{Workato::Connector::Sdk::WORKATO_API_EMAIL_ENV} "\
132
+ 'environment variable.'
132
133
  method_option :api_token,
133
134
  type: :string,
134
- desc: 'Token for accessing Workato API or ' \
135
- "set #{Workato::Connector::Sdk::WORKATO_API_TOKEN_ENV} environment variable"
135
+ desc: "Token for accessing Workato API.\n" \
136
+ "If present overrides value from #{Workato::Connector::Sdk::WORKATO_API_TOKEN_ENV} "\
137
+ 'environment variable.'
136
138
  method_option :environment,
137
139
  type: :string,
138
- enum: Workato::CLI::PushCommand::ENVIRONMENTS.keys,
139
- default: 'live',
140
- desc: 'Server to push connector code to'
140
+ desc: "Data center specific URL to push connector code.\n"\
141
+ "If present overrides value from #{Workato::Connector::Sdk::WORKATO_BASE_URL_ENV} "\
142
+ "environment variable.\n"\
143
+ "Examples: 'https://app.workato.com', 'https://app.eu.workato.com'"
141
144
  method_option :folder,
142
145
  type: :string,
143
146
  desc: 'Folder ID if you what to push to folder other than Home'
@@ -168,7 +171,7 @@ module Workato
168
171
  type: :string,
169
172
  aliases: '-k',
170
173
  lazy_default: Workato::Connector::Sdk::DEFAULT_MASTER_KEY_PATH,
171
- desc: 'Path to file with encrypt/decrypt key. '\
174
+ desc: "Path to file with encrypt/decrypt key.\n"\
172
175
  "NOTE: key from #{Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV} has higher priority"
173
176
  method_option :port,
174
177
  type: :string,
@@ -187,6 +190,35 @@ module Workato
187
190
  options: options
188
191
  ).call
189
192
  end
193
+
194
+ class << self
195
+ def print_options(shell, options, group_name = nil)
196
+ return if options.empty?
197
+
198
+ list = []
199
+ padding = options.map { |o| o.aliases.size }.max.to_i * 4
200
+
201
+ options.each do |option|
202
+ next if option.hide
203
+
204
+ description = []
205
+ description_lines = option.description ? option.description.split("\n") : []
206
+ first_line = description_lines.shift
207
+ description << [option.usage(padding), first_line ? "# #{first_line}" : '']
208
+ description_lines.each do |line|
209
+ description << ['', "# #{line}"]
210
+ end
211
+
212
+ list.concat(description)
213
+ list << ['', "# Default: #{option.default}"] if option.show_default?
214
+ list << ['', "# Possible values: #{option.enum.join(', ')}"] if option.enum
215
+ end
216
+
217
+ shell.say(group_name ? "#{group_name} options:" : 'Options:')
218
+ shell.print_table(list, indent: 2)
219
+ shell.say ''
220
+ end
221
+ end
190
222
  end
191
223
  end
192
224
  end
@@ -32,7 +32,9 @@ module Workato
32
32
 
33
33
  def initialize(options:)
34
34
  @options = options
35
- @api_base_url = ENVIRONMENTS.fetch(options[:environment])
35
+ @api_base_url = ENVIRONMENTS.fetch(options[:environment]) do
36
+ options[:environment].presence || Workato::Connector::Sdk::WORKATO_BASE_URL
37
+ end
36
38
  @api_email = options[:api_email] || ENV[Workato::Connector::Sdk::WORKATO_API_EMAIL_ENV]
37
39
  @api_token = options[:api_token] || ENV[Workato::Connector::Sdk::WORKATO_API_TOKEN_ENV]
38
40
  @folder_id = options[:folder]
@@ -9,7 +9,6 @@ module Workato
9
9
  SAMPLE_TO_SCHEMA_SUPPORT_TYPES = %w[csv json].freeze
10
10
  CSV_SEPARATORS = %w[comma space tab colon semicolon pipe].freeze
11
11
 
12
- WORKATO_BASE_URL = ENV['WORKATO_BASE_URL'] || 'https://app.workato.com'
13
12
  API_GENERATE_SCHEMA_PATH = '/api/sdk/generate_schema'
14
13
 
15
14
  def initialize(options:)
@@ -53,7 +52,7 @@ module Workato
53
52
  end
54
53
 
55
54
  def sample_to_schema(sample)
56
- url = "#{WORKATO_BASE_URL}#{API_GENERATE_SCHEMA_PATH}/#{sample.delete(:type)}"
55
+ url = "#{Workato::Connector::Sdk::WORKATO_BASE_URL}#{API_GENERATE_SCHEMA_PATH}/#{sample.delete(:type)}"
57
56
  response = RestClient.post(
58
57
  url,
59
58
  sample.to_json,
@@ -75,8 +74,7 @@ module Workato
75
74
  }
76
75
  end
77
76
 
78
- private_constant :API_GENERATE_SCHEMA_PATH,
79
- :WORKATO_BASE_URL
77
+ private_constant :API_GENERATE_SCHEMA_PATH
80
78
  end
81
79
  end
82
80
  end
@@ -202,6 +202,8 @@ module Workato
202
202
  ).returns(T::Boolean)
203
203
  end
204
204
  def refresh?(http_code, http_body, exception)
205
+ return false unless /oauth2/i =~ type || source[:acquire].present?
206
+
205
207
  refresh_on = self.refresh_on
206
208
  refresh_on.blank? || refresh_on.any? do |pattern|
207
209
  pattern.is_a?(::Integer) && pattern == http_code ||
@@ -7,9 +7,12 @@ module Workato
7
7
  module Dsl
8
8
  module Call
9
9
  def call(method, *args)
10
- raise InvalidDefinitionError, "method '#{method}' does not exists" unless @_methods[method]
10
+ method_proc = @_methods[method]
11
11
 
12
- instance_exec(*args, &@_methods[method])
12
+ raise UndefinedMethodError, method unless method_proc
13
+ raise UnexpectedMethodDefinitionError.new(method, method_proc) unless method_proc.is_a?(Proc)
14
+
15
+ instance_exec(*args, &method_proc)
13
16
  end
14
17
  end
15
18
  end
@@ -6,12 +6,51 @@ module Workato
6
6
  module Sdk
7
7
  InvalidDefinitionError = Class.new(StandardError)
8
8
 
9
- InvalidSchemaError = Class.new(StandardError)
9
+ class UnexpectedMethodDefinitionError < InvalidDefinitionError
10
+ attr_reader :name
11
+ attr_reader :definition
12
+
13
+ def initialize(name, definition)
14
+ super("Expected lambda for method '#{name}' definition, got: #{definition.class.name}")
15
+ @name = name
16
+ @definition = definition
17
+ end
18
+ end
19
+
20
+ class UndefinedMethodError < InvalidDefinitionError
21
+ attr_reader :name
22
+
23
+ def initialize(name)
24
+ super("Method '#{name}' does not exists")
25
+ @name = name
26
+ end
27
+ end
28
+
29
+ InvalidSchemaError = Class.new(InvalidDefinitionError)
10
30
 
11
31
  CustomRequestError = Class.new(StandardError)
12
32
 
13
33
  RuntimeError = Class.new(StandardError)
14
34
 
35
+ class UnresolvedObjectDefinitionError < StandardError
36
+ attr_reader :name
37
+
38
+ def initialize(name)
39
+ super("Cannot find object definition for '#{name}'")
40
+ @name = name
41
+ end
42
+ end
43
+
44
+ class CircleReferenceObjectDefinitionError < StandardError
45
+ attr_reader :name
46
+
47
+ def initialize(name, backtrace = [])
48
+ super("Infinite recursion occurred in object definition for '#{name}'")
49
+ set_backtrace(backtrace)
50
+ @name = name
51
+ end
52
+ end
53
+
15
54
  class RequestError < StandardError
16
55
  attr_reader :method
17
56
  attr_reader :code
@@ -41,6 +80,24 @@ module Workato
41
80
  super(message)
42
81
  end
43
82
  end
83
+
84
+ RequestTLSCertificateFormatError = Class.new(StandardError)
85
+
86
+ RequestPayloadFormatError = Class.new(StandardError)
87
+
88
+ JSONRequestFormatError = Class.new(RequestPayloadFormatError)
89
+
90
+ JSONResponseFormatError = Class.new(RequestPayloadFormatError)
91
+
92
+ XMLRequestFormatError = Class.new(RequestPayloadFormatError)
93
+
94
+ XMLResponseFormatError = Class.new(RequestPayloadFormatError)
95
+
96
+ WWWFormURLEncodedRequestFormatError = Class.new(RequestPayloadFormatError)
97
+
98
+ MultipartFormRequestFormatError = Class.new(RequestPayloadFormatError)
99
+
100
+ RAWResponseFormatError = Class.new(RequestPayloadFormatError)
44
101
  end
45
102
  end
46
103
  end
@@ -18,16 +18,22 @@ module Workato
18
18
 
19
19
  def lazy(settings = nil, config_fields = {})
20
20
  DupHashWithIndifferentAccess.new do |object_definitions, name|
21
- fields_proc = object_definitions_source[name][:fields]
22
- object_definitions[name] = Action.new(
23
- action: {
24
- execute: lambda do |connection, input|
25
- instance_exec(connection, input, object_definitions, &fields_proc)
26
- end
27
- },
28
- methods: methods_source,
29
- connection: connection
30
- ).execute(settings, config_fields)
21
+ fields_proc = object_definitions_source.dig(name, :fields)
22
+ raise Workato::Connector::Sdk::UnresolvedObjectDefinitionError, name unless fields_proc
23
+
24
+ begin
25
+ object_definitions[name] = Action.new(
26
+ action: {
27
+ execute: lambda do |connection, input|
28
+ instance_exec(connection, input, object_definitions, &fields_proc)
29
+ end
30
+ },
31
+ methods: methods_source,
32
+ connection: connection
33
+ ).execute(settings, config_fields)
34
+ rescue SystemStackError => e
35
+ raise Workato::Connector::Sdk::CircleReferenceObjectDefinitionError.new(name, e.backtrace)
36
+ end
31
37
  end
32
38
  end
33
39
 
@@ -9,6 +9,7 @@ require 'net/http'
9
9
  require 'net/http/digest_auth'
10
10
 
11
11
  require_relative './block_invocation_refinements'
12
+ require_relative './../../utilities/encoding'
12
13
 
13
14
  module Workato
14
15
  module Connector
@@ -27,7 +28,6 @@ module Workato
27
28
  @action = action
28
29
  @headers = {}
29
30
  @case_sensitive_headers = {}
30
- @params = {}.with_indifferent_access
31
31
  @render_request = ->(payload) { payload }
32
32
  @parse_response = ->(payload) { payload }
33
33
  @after_response = ->(_response_code, parsed_response, _response_headers) { parsed_response }
@@ -58,7 +58,7 @@ module Workato
58
58
  detect_error!(response.body)
59
59
  parsed_response = @parse_response.call(response)
60
60
  detect_error!(parsed_response)
61
- within_action_context(response.code, parsed_response, response.headers, &@after_response)
61
+ apply_after_response(response.code, parsed_response, response.headers)
62
62
  end
63
63
  )
64
64
  rescue RestClient::Exception => e
@@ -81,7 +81,12 @@ module Workato
81
81
  end
82
82
 
83
83
  def params(params)
84
- @params.merge!(params)
84
+ if params.is_a?(Hash)
85
+ @params ||= HashWithIndifferentAccess.new
86
+ @params.merge!(params)
87
+ else
88
+ @params = params
89
+ end
85
90
  self
86
91
  end
87
92
 
@@ -142,13 +147,17 @@ module Workato
142
147
 
143
148
  def request_format_json
144
149
  @content_type_header = :json
145
- @render_request = ->(payload) { ActiveSupport::JSON.encode(payload) if payload }
150
+ @render_request = lambda_with_error_wrap(JSONRequestFormatError) do |payload|
151
+ ActiveSupport::JSON.encode(payload) if payload
152
+ end
146
153
  self
147
154
  end
148
155
 
149
156
  def response_format_json
150
157
  @accept_header = :json
151
- @parse_response = ->(payload) { ActiveSupport::JSON.decode(payload.presence || '{}') }
158
+ @parse_response = lambda_with_error_wrap(JSONResponseFormatError) do |payload|
159
+ ActiveSupport::JSON.decode(payload.presence || '{}')
160
+ end
152
161
  self
153
162
  end
154
163
 
@@ -158,17 +167,19 @@ module Workato
158
167
 
159
168
  def request_format_xml(root_element_name, namespaces = {})
160
169
  @content_type_header = :xml
161
- @render_request = Kernel.lambda { |payload|
170
+ @render_request = lambda_with_error_wrap(XMLRequestFormatError) do |payload|
162
171
  next unless payload
163
172
 
164
173
  Gyoku.xml({ root_element_name => payload.merge(namespaces).deep_symbolize_keys }, key_converter: :none)
165
- }
174
+ end
166
175
  self
167
176
  end
168
177
 
169
178
  def response_format_xml(strip_response_namespaces: false)
170
179
  @accept_header = :xml
171
- @parse_response = ->(payload) { Xml.parse_xml_to_hash(payload, strip_namespaces: strip_response_namespaces) }
180
+ @parse_response = lambda_with_error_wrap(XMLResponseFormatError) do |payload|
181
+ Xml.parse_xml_to_hash(payload, strip_namespaces: strip_response_namespaces)
182
+ end
172
183
  self
173
184
  end
174
185
 
@@ -179,7 +190,7 @@ module Workato
179
190
  end
180
191
 
181
192
  def response_format_raw
182
- @parse_response = Kernel.lambda do |payload|
193
+ @parse_response = lambda_with_error_wrap(RAWResponseFormatError) do |payload|
183
194
  payload.body.force_encoding(::Encoding::BINARY)
184
195
  payload.body.valid_encoding? ? payload.body : payload.body.force_encoding(::Encoding::BINARY)
185
196
  end
@@ -189,7 +200,7 @@ module Workato
189
200
  def request_format_multipart_form
190
201
  @content_type_header = nil
191
202
 
192
- @render_request = Kernel.lambda do |payload|
203
+ @render_request = lambda_with_error_wrap(MultipartFormRequestFormatError) do |payload|
193
204
  payload&.each_with_object({}) do |(name, (value, content_type, original_filename)), rendered|
194
205
  rendered[name] = if content_type.present?
195
206
  Part.new(name, content_type, original_filename || ::File.basename(name), value.to_s)
@@ -204,7 +215,7 @@ module Workato
204
215
 
205
216
  def request_format_www_form_urlencoded
206
217
  @content_type_header = 'application/x-www-form-urlencoded'
207
- @render_request = Kernel.lambda { |payload| payload.to_param }
218
+ @render_request = lambda_with_error_wrap(WWWFormURLEncodedRequestFormatError, &:to_param)
208
219
  self
209
220
  end
210
221
 
@@ -228,6 +239,8 @@ module Workato
228
239
  OpenSSL::X509::Certificate.new(intermediate)
229
240
  end
230
241
  self
242
+ rescue OpenSSL::OpenSSLError => e
243
+ Kernel.raise(RequestTLSCertificateFormatError, e)
231
244
  end
232
245
 
233
246
  def tls_server_certs(certificates:, strict: true)
@@ -237,12 +250,18 @@ module Workato
237
250
  @ssl_cert_store.add_cert(OpenSSL::X509::Certificate.new(certificate))
238
251
  end
239
252
  self
253
+ rescue OpenSSL::OpenSSLError => e
254
+ Kernel.raise(RequestTLSCertificateFormatError, e)
240
255
  end
241
256
 
242
257
  def puts(*args)
243
258
  ::Kernel.puts(*args)
244
259
  end
245
260
 
261
+ def try(*args, &block)
262
+ execute!.try(*args, &block)
263
+ end
264
+
246
265
  private
247
266
 
248
267
  attr_reader :method
@@ -295,14 +314,14 @@ module Workato
295
314
  URI.parse(@uri)
296
315
  end
297
316
 
298
- return uri.to_s unless @params.any? || @user || @password
317
+ return uri.to_s unless @params || @user || @password
299
318
 
300
319
  unless @digest_auth
301
320
  uri.user = URI.encode_www_form_component(@user) if @user
302
321
  uri.password = URI.encode_www_form_component(@password) if @password
303
322
  end
304
323
 
305
- return uri.to_s unless @params.any?
324
+ return uri.to_s unless @params
306
325
 
307
326
  query = uri.query.to_s.split('&').select(&:present?).join('&').presence
308
327
  params = @params.to_param.presence
@@ -360,6 +379,13 @@ module Workato
360
379
  )
361
380
  end
362
381
 
382
+ def apply_after_response(code, parsed_response, headers)
383
+ encoded_headers = (headers || {}).each_with_object(HashWithIndifferentAccess.new) do |(k, v), h|
384
+ h[k] = Workato::Utilities::Encoding.force_best_encoding!(v.to_s)
385
+ end
386
+ within_action_context(code, parsed_response, encoded_headers, &@after_response)
387
+ end
388
+
363
389
  def within_action_context(*args, &block)
364
390
  (@action || self).instance_exec(*args, &block)
365
391
  end
@@ -384,7 +410,7 @@ module Workato
384
410
  instance_exec(settings, @auth_type, &apply)
385
411
  end
386
412
  yield
387
- rescue StandardError => e
413
+ rescue RestClient::Exception, CustomRequestError => e
388
414
  Kernel.raise e unless first
389
415
  Kernel.raise e unless refresh_authorization!(e.try(:http_code), e.try(:http_body), e.message)
390
416
 
@@ -413,6 +439,16 @@ module Workato
413
439
  T.must(@connection)
414
440
  end
415
441
 
442
+ def lambda_with_error_wrap(error_type, &block)
443
+ Kernel.lambda do |payload|
444
+ begin
445
+ block.call(payload)
446
+ rescue StandardError => e
447
+ Kernel.raise error_type.new(e)
448
+ end
449
+ end
450
+ end
451
+
416
452
  class Part < StringIO
417
453
  def initialize(path, content_type, original_filename, *args)
418
454
  super(*args)
@@ -8,6 +8,20 @@ module Workato
8
8
  module Sdk
9
9
  module SorbetTypes
10
10
  WebhookSubscribeOutputHash = T.type_alias { T::Hash[T.any(String, Symbol), T.untyped] }
11
+
12
+ WebhookNotificationPayload = T.type_alias { T.untyped }
13
+
14
+ TriggerEventHash = T.type_alias { T::Hash[T.untyped, T.untyped] }
15
+
16
+ WebhookNotificationOutputHash = T.type_alias { T.any(T::Array[TriggerEventHash], TriggerEventHash) }
17
+
18
+ PollOutputHash = T.type_alias do
19
+ {
20
+ 'events' => T::Array[TriggerEventHash],
21
+ 'can_poll_more' => T.nilable(T::Boolean),
22
+ 'next_poll' => T.untyped
23
+ }
24
+ end
11
25
  end
12
26
 
13
27
  class Trigger < Operation
@@ -38,7 +52,7 @@ module Workato
38
52
  extended_input_schema: SorbetTypes::OperationSchema,
39
53
  extended_output_schema: SorbetTypes::OperationSchema
40
54
  ).returns(
41
- HashWithIndifferentAccess
55
+ SorbetTypes::PollOutputHash
42
56
  )
43
57
  end
44
58
  def poll_page(settings = nil, input = {}, closure = nil, extended_input_schema = [],
@@ -52,7 +66,10 @@ module Workato
52
66
  ) do |connection, payload, eis, eos|
53
67
  instance_exec(connection, payload[:input], payload[:closure], eis, eos, &poll_proc)
54
68
  end
55
- output[:events] = ::Array.wrap(output[:events]).reverse!.uniq(&trigger[:dedup])
69
+ output.with_indifferent_access
70
+ output[:events] = Array.wrap(output[:events])
71
+ .reverse!
72
+ .map! { |event| ::Hash.try_convert(event) || event }
56
73
  output[:next_poll] = output[:next_poll].presence || closure
57
74
  output
58
75
  end
@@ -65,11 +82,11 @@ module Workato
65
82
  extended_input_schema: SorbetTypes::OperationSchema,
66
83
  extended_output_schema: SorbetTypes::OperationSchema
67
84
  ).returns(
68
- HashWithIndifferentAccess
85
+ SorbetTypes::PollOutputHash
69
86
  )
70
87
  end
71
88
  def poll(settings = nil, input = {}, closure = nil, extended_input_schema = [], extended_output_schema = [])
72
- events = T.let([], T::Array[T::Hash[T.any(String, Symbol), T.untyped]])
89
+ events = T.let([], T::Array[SorbetTypes::TriggerEventHash])
73
90
 
74
91
  loop do
75
92
  output = poll_page(settings, input, closure, extended_input_schema, extended_output_schema)
@@ -80,13 +97,13 @@ module Workato
80
97
  end
81
98
 
82
99
  {
83
- events: events.uniq(&trigger[:dedup]),
100
+ events: events,
84
101
  can_poll_more: false,
85
102
  next_poll: closure
86
103
  }.with_indifferent_access
87
104
  end
88
105
 
89
- sig { params(input: SorbetTypes::OperationInputHash).returns(T.untyped) }
106
+ sig { params(input: SorbetTypes::TriggerEventHash).returns(T.untyped) }
90
107
  def dedup(input = {})
91
108
  trigger[:dedup].call(input)
92
109
  end
@@ -94,7 +111,7 @@ module Workato
94
111
  sig do
95
112
  params(
96
113
  input: SorbetTypes::OperationInputHash,
97
- payload: T::Hash[T.any(String, Symbol), T.untyped],
114
+ payload: SorbetTypes::WebhookNotificationPayload,
98
115
  extended_input_schema: SorbetTypes::OperationSchema,
99
116
  extended_output_schema: SorbetTypes::OperationSchema,
100
117
  headers: T::Hash[T.any(String, Symbol), T.untyped],
@@ -102,7 +119,7 @@ module Workato
102
119
  settings: T.nilable(SorbetTypes::SettingsHash),
103
120
  webhook_subscribe_output: SorbetTypes::WebhookSubscribeOutputHash
104
121
  ).returns(
105
- HashWithIndifferentAccess
122
+ SorbetTypes::WebhookNotificationOutputHash
106
123
  )
107
124
  end
108
125
  def webhook_notification(
@@ -116,10 +133,10 @@ module Workato
116
133
  webhook_subscribe_output = {}
117
134
  )
118
135
  connection.merge_settings!(settings) if settings
119
- Dsl::WithDsl.execute(
136
+ output = Dsl::WithDsl.execute(
120
137
  connection,
121
138
  input.with_indifferent_access,
122
- payload.with_indifferent_access,
139
+ payload,
123
140
  Array.wrap(extended_input_schema).map(&:with_indifferent_access),
124
141
  Array.wrap(extended_output_schema).map(&:with_indifferent_access),
125
142
  headers.with_indifferent_access,
@@ -127,7 +144,12 @@ module Workato
127
144
  connection.settings,
128
145
  webhook_subscribe_output.with_indifferent_access,
129
146
  &trigger[:webhook_notification]
130
- ).with_indifferent_access
147
+ )
148
+ if output.is_a?(::Array)
149
+ output.map! { |event| ::Hash.try_convert(event) || event }
150
+ else
151
+ ::Hash.try_convert(output) || output
152
+ end
131
153
  end
132
154
 
133
155
  sig do
@@ -169,7 +191,7 @@ module Workato
169
191
  params: T::Hash[T.any(String, Symbol), T.untyped],
170
192
  webhook_subscribe_output: SorbetTypes::WebhookSubscribeOutputHash
171
193
  ).returns(
172
- T::Hash[T.any(String, Symbol), T.untyped]
194
+ T.any(SorbetTypes::WebhookNotificationOutputHash, SorbetTypes::PollOutputHash)
173
195
  )
174
196
  end
175
197
  def invoke(input = {}, payload = {}, headers = {}, params = {}, webhook_subscribe_output = {})
@@ -4,7 +4,7 @@
4
4
  module Workato
5
5
  module Connector
6
6
  module Sdk
7
- VERSION = '1.0.2'
7
+ VERSION = '1.1.0'
8
8
  end
9
9
  end
10
10
  end
@@ -26,6 +26,10 @@ module Workato
26
26
 
27
27
  WORKATO_API_EMAIL_ENV = 'WORKATO_API_EMAIL'
28
28
  WORKATO_API_TOKEN_ENV = 'WORKATO_API_TOKEN'
29
+
30
+ WORKATO_BASE_URL_ENV = 'WORKATO_BASE_URL'
31
+ DEFAULT_WORKATO_BASE_URL = 'https://app.workato.com'
32
+ WORKATO_BASE_URL = T.let(ENV.fetch(WORKATO_BASE_URL_ENV, DEFAULT_WORKATO_BASE_URL), String)
29
33
  end
30
34
  end
31
35
  end
@@ -5,7 +5,7 @@ module Workato
5
5
  module Extension
6
6
  module Currency
7
7
  def to_currency(options = {})
8
- ActiveSupport::NumberHelper::NumberToCurrencyConverter(self, options)
8
+ ActiveSupport::NumberHelper::NumberToCurrencyConverter.convert(self, options)
9
9
  end
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module Workato
5
5
  module Extension
6
6
  module Phone
7
7
  def to_phone(options = {})
8
- ActiveSupport::NumberHelper::NumberToPhoneConverter(self, options).to_s
8
+ ActiveSupport::NumberHelper::NumberToPhoneConverter.convert(self, options).to_s
9
9
  end
10
10
  end
11
11
  end
@@ -1,6 +1,8 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'rails-html-sanitizer'
5
+
4
6
  module Workato
5
7
  module Extension
6
8
  module String
@@ -36,7 +38,8 @@ module Workato
36
38
  end
37
39
 
38
40
  def strip_tags
39
- Loofah::Helpers.strip_tags(self)
41
+ @html_full_sanitizer ||= Rails::Html::Sanitizer.full_sanitizer.new
42
+ @html_full_sanitizer.sanitize(self)
40
43
  end
41
44
 
42
45
  def to_time(form = :local, format: nil)
@@ -0,0 +1,57 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'charlock_holmes'
5
+
6
+ module Workato
7
+ module Utilities
8
+ module Encoding
9
+ class << self
10
+ # this function finds first possible encoding that allows to perform correct encoding operations
11
+ # if no encoding is found it preserves initial and replaces bad symbols with ?
12
+ def force_best_encoding!(string)
13
+ original_encoding = string.encoding
14
+
15
+ possible_encodings(string).each do |encoding|
16
+ return string.force_encoding(::Encoding::ASCII_8BIT) if encoding.nil? # for binary
17
+ next unless string.force_encoding(encoding).valid_encoding?
18
+
19
+ begin
20
+ # try encode to utf
21
+ string.encode(::Encoding::UTF_8)
22
+ return string
23
+ rescue ::Encoding::UndefinedConversionError
24
+ next
25
+ end
26
+ end
27
+ if original_encoding == ::Encoding::BINARY
28
+ string.force_encoding(::Encoding::BINARY)
29
+ else
30
+ string
31
+ .encode!(::Encoding::UTF_8, invalid: :replace, undef: :replace, replace: '?')
32
+ .encode!(original_encoding, invalid: :replace, undef: :replace, replace: '?')
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def possible_encodings(string)
39
+ encoding_candidates = CharlockHolmes::EncodingDetector.detect_all(string).sort! do |a, b|
40
+ confidence_a, encoding_a = a.values_at(:confidence, :ruby_encoding)
41
+ confidence_b, encoding_b = b.values_at(:confidence, :ruby_encoding)
42
+ # If equal and one binary, prefer non-binary.
43
+ if confidence_a == confidence_b
44
+ if encoding_a == 'binary'
45
+ confidence_b += 100
46
+ elsif encoding_b == 'binary'
47
+ confidence_a += 100
48
+ end
49
+ end
50
+ confidence_b <=> confidence_a
51
+ end
52
+ encoding_candidates.map { |candidate| candidate[:ruby_encoding] }
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workato-connector-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Abolmasov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-08 00:00:00.000000000 Z
11
+ date: 2022-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.2.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: charlock_holmes
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.7.7
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.7.7
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: countries
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +150,6 @@ dependencies:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
152
  version: '2.0'
139
- - !ruby/object:Gem::Dependency
140
- name: loofah
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - '='
144
- - !ruby/object:Gem::Version
145
- version: 2.16.0
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - '='
151
- - !ruby/object:Gem::Version
152
- version: 2.16.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: net-http-digest_auth
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: '2.0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: rails-html-sanitizer
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - '='
214
+ - !ruby/object:Gem::Version
215
+ version: 1.4.3
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - '='
221
+ - !ruby/object:Gem::Version
222
+ version: 1.4.3
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: rest-client
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -491,6 +505,7 @@ files:
491
505
  - lib/workato/extension/time.rb
492
506
  - lib/workato/testing/vcr_encrypted_cassette_serializer.rb
493
507
  - lib/workato/testing/vcr_multipart_body_matcher.rb
508
+ - lib/workato/utilities/encoding.rb
494
509
  - lib/workato/web/app.rb
495
510
  - templates/.rspec.erb
496
511
  - templates/Gemfile.erb
@@ -525,8 +540,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
525
540
  - !ruby/object:Gem::Version
526
541
  version: '0'
527
542
  requirements: []
528
- rubyforge_project:
529
- rubygems_version: 2.6.14.4
543
+ rubygems_version: 3.2.3
530
544
  signing_key:
531
545
  specification_version: 4
532
546
  summary: Gem for running adapter's code outside Workato infrastructure