workato-connector-sdk 1.1.0 → 1.3.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/VERSION +1 -0
  4. data/lib/workato/cli/edit_command.rb +3 -1
  5. data/lib/workato/cli/exec_command.rb +103 -17
  6. data/lib/workato/cli/generate_command.rb +2 -2
  7. data/lib/workato/cli/main.rb +17 -10
  8. data/lib/workato/cli/multi_auth_selected_fallback.rb +33 -0
  9. data/lib/workato/cli/oauth2_command.rb +50 -12
  10. data/lib/workato/cli/push_command.rb +2 -2
  11. data/lib/workato/cli/schema_command.rb +2 -2
  12. data/lib/workato/connector/sdk/account_properties.rb +2 -2
  13. data/lib/workato/connector/sdk/action.rb +20 -70
  14. data/lib/workato/connector/sdk/block_invocation_refinements.rb +2 -10
  15. data/lib/workato/connector/sdk/connection.rb +115 -30
  16. data/lib/workato/connector/sdk/connector.rb +71 -81
  17. data/lib/workato/connector/sdk/core.rb +62 -0
  18. data/lib/workato/connector/sdk/dsl/aws.rb +8 -5
  19. data/lib/workato/connector/sdk/dsl/call.rb +1 -1
  20. data/lib/workato/connector/sdk/dsl/csv_package.rb +133 -0
  21. data/lib/workato/connector/sdk/dsl/execution_context.rb +45 -0
  22. data/lib/workato/connector/sdk/dsl/http.rb +1 -1
  23. data/lib/workato/connector/sdk/dsl/reinvoke_after.rb +84 -0
  24. data/lib/workato/connector/sdk/dsl/stream_package.rb +65 -0
  25. data/lib/workato/connector/sdk/dsl/time.rb +0 -14
  26. data/lib/workato/connector/sdk/dsl/workato_package.rb +146 -0
  27. data/lib/workato/connector/sdk/dsl.rb +64 -10
  28. data/lib/workato/connector/sdk/errors.rb +37 -9
  29. data/lib/workato/connector/sdk/lookup_tables.rb +3 -1
  30. data/lib/workato/connector/sdk/operation.rb +33 -10
  31. data/lib/workato/connector/sdk/request.rb +149 -69
  32. data/lib/workato/connector/sdk/schema/field/convertors.rb +2 -2
  33. data/lib/workato/connector/sdk/schema/type/unicode_string.rb +1 -1
  34. data/lib/workato/connector/sdk/schema.rb +12 -8
  35. data/lib/workato/connector/sdk/settings.rb +14 -3
  36. data/lib/workato/connector/sdk/stream.rb +243 -0
  37. data/lib/workato/connector/sdk/streams.rb +71 -0
  38. data/lib/workato/connector/sdk/summarize.rb +2 -2
  39. data/lib/workato/connector/sdk/trigger.rb +23 -15
  40. data/lib/workato/connector/sdk/version.rb +1 -1
  41. data/lib/workato/connector/sdk.rb +21 -47
  42. data/lib/workato/extension/array.rb +2 -0
  43. data/lib/workato/extension/case_sensitive_headers.rb +0 -26
  44. data/lib/workato/extension/content_encoding_decoder.rb +69 -0
  45. data/lib/workato/extension/currency/countries.rb +79 -0
  46. data/lib/workato/extension/currency/countries.yml +18433 -0
  47. data/lib/workato/extension/currency/currencies.rb +55 -0
  48. data/lib/workato/extension/currency/currencies.yml +479 -0
  49. data/lib/workato/extension/currency.rb +73 -5
  50. data/lib/workato/extension/enumerable.rb +2 -2
  51. data/lib/workato/extension/extra_chain_cert.rb +0 -14
  52. data/lib/workato/extension/hash_with_indifferent_access.rb +19 -0
  53. data/lib/workato/extension/metadata_fix_wrap_kw_args.rb +11 -0
  54. data/lib/workato/extension/string.rb +16 -112
  55. data/lib/workato/testing/vcr_encrypted_cassette_serializer.rb +2 -0
  56. data/lib/workato/types/binary.rb +55 -0
  57. data/lib/workato/{connector/sdk → utilities}/xml.rb +4 -4
  58. metadata +61 -64
  59. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +0 -160
@@ -7,9 +7,13 @@ require 'json'
7
7
  require 'gyoku'
8
8
  require 'net/http'
9
9
  require 'net/http/digest_auth'
10
+ require 'active_support/json'
10
11
 
12
+ require 'workato/utilities/encoding'
13
+ require 'workato/utilities/xml'
11
14
  require_relative './block_invocation_refinements'
12
- require_relative './../../utilities/encoding'
15
+
16
+ using Workato::Extension::HashWithIndifferentAccess
13
17
 
14
18
  module Workato
15
19
  module Connector
@@ -31,43 +35,34 @@ module Workato
31
35
  @render_request = ->(payload) { payload }
32
36
  @parse_response = ->(payload) { payload }
33
37
  @after_response = ->(_response_code, parsed_response, _response_headers) { parsed_response }
38
+ @callstack_before_request = Array.wrap(Kernel.caller)
34
39
  end
35
40
 
36
- def method_missing(*args, &block)
37
- execute!.send(*args, &block)
41
+ def method_missing(...)
42
+ execute!.send(...)
38
43
  end
39
44
 
40
45
  def execute!
41
- __getobj__ || __setobj__(
42
- authorized do
43
- begin
44
- request = build_request
45
- response = execute(request)
46
- rescue RestClient::Unauthorized => e
47
- Kernel.raise e unless @digest_auth
48
-
49
- @digest_auth = false
50
- headers('Authorization' => Net::HTTP::DigestAuth.new.auth_header(
51
- URI.parse(build_url),
52
- e.response.headers[:www_authenticate],
53
- method.to_s.upcase
54
- ))
55
- request = build_request
56
- response = execute(request)
57
- end
58
- detect_error!(response.body)
59
- parsed_response = @parse_response.call(response)
60
- detect_error!(parsed_response)
61
- apply_after_response(response.code, parsed_response, response.headers)
62
- end
63
- )
46
+ __getobj__ || __setobj__(response)
47
+ rescue RestClient::Exceptions::Timeout => e
48
+ Kernel.raise RequestTimeoutError, e
64
49
  rescue RestClient::Exception => e
65
50
  if after_error_response_matches?(e)
66
51
  return apply_after_error_response(e)
67
52
  end
68
53
 
69
- Kernel.raise RequestError.new(response: e.response, message: e.message, method: current_verb,
70
- code: e.http_code)
54
+ Kernel.raise RequestFailedError.new(
55
+ response: e.response,
56
+ message: e.message,
57
+ method: current_verb,
58
+ code: e.http_code
59
+ )
60
+ rescue StandardError => e
61
+ error_backtrace = Array.wrap(e.backtrace)
62
+ first_call_after_request_idx = error_backtrace.rindex { |s| s.start_with?(__FILE__) }
63
+ error_backtrace_after_request = error_backtrace[0..first_call_after_request_idx]
64
+ e.set_backtrace(error_backtrace_after_request + @callstack_before_request)
65
+ Kernel.raise e
71
66
  end
72
67
 
73
68
  def headers(headers)
@@ -178,7 +173,7 @@ module Workato
178
173
  def response_format_xml(strip_response_namespaces: false)
179
174
  @accept_header = :xml
180
175
  @parse_response = lambda_with_error_wrap(XMLResponseFormatError) do |payload|
181
- Xml.parse_xml_to_hash(payload, strip_namespaces: strip_response_namespaces)
176
+ Workato::Utilities::Xml.parse_xml_to_hash(payload, strip_namespaces: strip_response_namespaces)
182
177
  end
183
178
  self
184
179
  end
@@ -258,15 +253,39 @@ module Workato
258
253
  ::Kernel.puts(*args)
259
254
  end
260
255
 
261
- def try(*args, &block)
262
- execute!.try(*args, &block)
256
+ def try(...)
257
+ execute!.try(...)
263
258
  end
264
259
 
265
260
  private
266
261
 
267
262
  attr_reader :method
268
263
 
269
- def execute(request)
264
+ def response
265
+ authorized do
266
+ begin
267
+ request = RestClientRequest.new(rest_request_params)
268
+ response = execute_request(request)
269
+ rescue RestClient::Unauthorized => e
270
+ Kernel.raise e unless @digest_auth
271
+
272
+ @digest_auth = false
273
+ headers('Authorization' => Net::HTTP::DigestAuth.new.auth_header(
274
+ URI.parse(build_url),
275
+ e.response.headers[:www_authenticate],
276
+ method.to_s.upcase
277
+ ))
278
+ request = RestClientRequest.new(rest_request_params)
279
+ response = execute_request(request)
280
+ end
281
+ detect_auth_error!(response.body)
282
+ parsed_response = @parse_response.call(response)
283
+ detect_auth_error!(parsed_response)
284
+ apply_after_response(response.code, parsed_response, response.headers)
285
+ end
286
+ end
287
+
288
+ def execute_request(request)
270
289
  if @follow_redirection.nil?
271
290
  request.execute
272
291
  else
@@ -285,31 +304,28 @@ module Workato
285
304
  end
286
305
  end
287
306
 
288
- def build_request
289
- RestClient::Request.new(
290
- {
291
- method: method,
292
- url: build_url,
293
- headers: build_headers,
294
- payload: @render_request.call(@payload)
295
- }.tap do |request_hash|
296
- if @ssl_client_cert.present? && @ssl_client_key.present?
297
- request_hash[:ssl_client_cert] = @ssl_client_cert
298
- request_hash[:ssl_client_key] = @ssl_client_key
307
+ def rest_request_params
308
+ {
309
+ method: method,
310
+ url: build_url,
311
+ headers: build_headers,
312
+ payload: @render_request.call(@payload),
313
+ case_sensitive_headers: @case_sensitive_headers.transform_keys(&:to_s)
314
+ }.tap do |request_hash|
315
+ if @ssl_client_cert.present? && @ssl_client_key.present?
316
+ request_hash[:ssl_client_cert] = @ssl_client_cert
317
+ request_hash[:ssl_client_key] = @ssl_client_key
318
+ if @ssl_client_intermediate_certs.present?
319
+ request_hash[:ssl_extra_chain_cert] = @ssl_client_intermediate_certs
299
320
  end
300
- request_hash[:ssl_cert_store] = @ssl_cert_store if @ssl_cert_store
301
- end
302
- ).tap do |request|
303
- request.case_sensitive_headers = @case_sensitive_headers.transform_keys(&:to_s)
304
- if @ssl_client_intermediate_certs.present? && @ssl_client_cert.present? && @ssl_client_key.present?
305
- request.extra_chain_cert = @ssl_client_intermediate_certs
306
321
  end
322
+ request_hash[:ssl_cert_store] = @ssl_cert_store if @ssl_cert_store
307
323
  end
308
324
  end
309
325
 
310
326
  def build_url
311
327
  uri = if @base_uri
312
- URI.parse(@base_uri).merge(@uri)
328
+ merge_uris(@base_uri, @uri)
313
329
  else
314
330
  URI.parse(@uri)
315
331
  end
@@ -334,9 +350,13 @@ module Workato
334
350
  uri.to_s
335
351
  end
336
352
 
353
+ def merge_uris(uri1, uri2)
354
+ (uri1.is_a?(::String) ? URI.parse(uri1) : uri1).merge(uri2)
355
+ end
356
+
337
357
  def build_headers
338
358
  headers = @headers
339
- if @content_type_header.present? && headers.keys.none? { |key| /^content[\-_]type$/i =~ key }
359
+ if @content_type_header.present? && headers.keys.none? { |key| /^content[-_]type$/i =~ key }
340
360
  headers[:content_type] = @content_type_header
341
361
  end
342
362
  if @accept_header && headers.keys.none? { |key| /^accept$/i =~ key }
@@ -345,13 +365,13 @@ module Workato
345
365
  headers.compact
346
366
  end
347
367
 
348
- def detect_error!(response)
368
+ def detect_auth_error!(response)
349
369
  return unless authorized?
350
370
 
351
371
  error_patterns = connection.authorization.detect_on
352
372
  return unless error_patterns.any? { |pattern| pattern === response rescue false }
353
373
 
354
- Kernel.raise(CustomRequestError, response.to_s)
374
+ Kernel.raise(DetectOnUnauthorizedRequestError, response.to_s)
355
375
  end
356
376
 
357
377
  def after_error_response_matches?(exception)
@@ -373,7 +393,7 @@ module Workato
373
393
  within_action_context(
374
394
  exception.http_code,
375
395
  exception.http_body,
376
- exception.http_headers&.with_indifferent_access || {},
396
+ HashWithIndifferentAccess.wrap(exception.http_headers),
377
397
  exception.message,
378
398
  &@after_error_response
379
399
  )
@@ -386,8 +406,8 @@ module Workato
386
406
  within_action_context(code, parsed_response, encoded_headers, &@after_response)
387
407
  end
388
408
 
389
- def within_action_context(*args, &block)
390
- (@action || self).instance_exec(*args, &block)
409
+ def within_action_context(...)
410
+ (@action || self).instance_exec(...)
391
411
  end
392
412
 
393
413
  sig { returns(T::Boolean) }
@@ -404,15 +424,15 @@ module Workato
404
424
  first = true
405
425
  begin
406
426
  settings = connection.settings
407
- if /oauth2/i =~ connection.authorization.type
408
- instance_exec(settings, settings[:access_token], @auth_type, &apply)
427
+ if connection.authorization.oauth2?
428
+ apply_oauth2(settings, settings[:access_token], @auth_type, &apply)
409
429
  else
410
- instance_exec(settings, @auth_type, &apply)
430
+ apply_custom_auth(settings, @auth_type, &apply)
411
431
  end
412
432
  yield
413
- rescue RestClient::Exception, CustomRequestError => e
433
+ rescue RestClient::Exception, DetectOnUnauthorizedRequestError => e
414
434
  Kernel.raise e unless first
415
- Kernel.raise e unless refresh_authorization!(e.try(:http_code), e.try(:http_body), e.message)
435
+ Kernel.raise e unless refresh_authorization!(settings, e.try(:http_code), e.try(:http_body), e.message)
416
436
 
417
437
  first = false
418
438
  retry
@@ -421,16 +441,49 @@ module Workato
421
441
 
422
442
  sig do
423
443
  params(
444
+ settings: HashWithIndifferentAccess,
445
+ access_token: T.untyped,
446
+ auth_type: T.untyped,
447
+ apply_proc: T.proc.params(
448
+ settings: HashWithIndifferentAccess,
449
+ access_token: T.untyped,
450
+ auth_type: T.untyped
451
+ ).void
452
+ ).void
453
+ end
454
+ def apply_oauth2(settings, access_token, auth_type, &apply_proc)
455
+ instance_exec(settings, access_token, auth_type, &apply_proc)
456
+ end
457
+
458
+ sig do
459
+ params(
460
+ settings: HashWithIndifferentAccess,
461
+ auth_type: T.untyped,
462
+ apply_proc: T.proc.params(
463
+ settings: HashWithIndifferentAccess,
464
+ auth_type: T.untyped
465
+ ).void
466
+ ).void
467
+ end
468
+ def apply_custom_auth(settings, auth_type, &apply_proc)
469
+ instance_exec(settings, auth_type, &apply_proc)
470
+ end
471
+
472
+ sig do
473
+ params(
474
+ settings_before: HashWithIndifferentAccess,
424
475
  http_code: T.nilable(Integer),
425
476
  http_body: T.nilable(String),
426
477
  exception: T.nilable(String)
427
478
  ).returns(T::Boolean)
428
479
  end
429
- def refresh_authorization!(http_code, http_body, exception)
480
+ def refresh_authorization!(settings_before, http_code, http_body, exception)
430
481
  return false unless connection.authorization.refresh?(http_code, http_body, exception)
431
482
 
432
- connection.update_settings!("Refresh token triggered on response \"#{exception}\"") do
433
- connection.authorization.refresh!(connection.settings)
483
+ connection.update_settings!("Refresh token triggered on response \"#{exception}\"", settings_before) do
484
+ next connection.settings unless connection.settings == settings_before
485
+
486
+ connection.authorization.refresh!(settings_before)
434
487
  end
435
488
  end
436
489
 
@@ -441,11 +494,9 @@ module Workato
441
494
 
442
495
  def lambda_with_error_wrap(error_type, &block)
443
496
  Kernel.lambda do |payload|
444
- begin
445
- block.call(payload)
446
- rescue StandardError => e
447
- Kernel.raise error_type.new(e)
448
- end
497
+ block.call(payload)
498
+ rescue StandardError => e
499
+ Kernel.raise error_type.new(e)
449
500
  end
450
501
  end
451
502
 
@@ -461,6 +512,35 @@ module Workato
461
512
  attr_reader :content_type
462
513
  attr_reader :original_filename
463
514
  end
515
+
516
+ class RestClientRequest < ::RestClient::Request
517
+ def initialize(args)
518
+ super
519
+ @ssl_opts[:extra_chain_cert] = args[:ssl_extra_chain_cert] if args.key?(:ssl_extra_chain_cert)
520
+ @case_sensitive_headers = args[:case_sensitive_headers]
521
+ @before_execution_proc = proc do |net_http_request, _args|
522
+ net_http_request.case_sensitive_headers = args[:case_sensitive_headers]
523
+ end
524
+ end
525
+
526
+ def ssl_extra_chain_cert
527
+ @ssl_opts[:extra_chain_cert]
528
+ end
529
+
530
+ def processed_headers
531
+ return @processed_headers if @case_sensitive_headers.blank?
532
+ return @case_sensitive_headers if @processed_headers.blank?
533
+
534
+ @processed_headers.merge(@case_sensitive_headers)
535
+ end
536
+
537
+ def net_http_object(hostname, port)
538
+ net = super(hostname, port)
539
+ net.extra_chain_cert = ssl_extra_chain_cert if ssl_extra_chain_cert
540
+ net
541
+ end
542
+ end
543
+ private_constant :RestClientRequest
464
544
  end
465
545
  end
466
546
  end
@@ -26,7 +26,7 @@ module Workato
26
26
  end
27
27
 
28
28
  def integer_conversion(value)
29
- value.try(:is_number?) && value.to_i || value
29
+ (value.try(:is_number?) && value.to_i) || value
30
30
  end
31
31
 
32
32
  def boolean_conversion(value)
@@ -34,7 +34,7 @@ module Workato
34
34
  end
35
35
 
36
36
  def float_conversion(value)
37
- value.try(:is_number?) && value.to_f || value
37
+ (value.try(:is_number?) && value.to_f) || value
38
38
  end
39
39
 
40
40
  def item_array_wrap(items)
@@ -8,7 +8,7 @@ module Workato
8
8
  module Type
9
9
  class UnicodeString < ::String
10
10
  def initialize(str)
11
- super(str, {})
11
+ super(str, **{})
12
12
  encode!('UTF-8')
13
13
  end
14
14
 
@@ -1,20 +1,22 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
+ using Workato::Extension::HashWithIndifferentAccess
5
+
4
6
  module Workato
5
7
  module Connector
6
8
  module Sdk
7
9
  class Schema < SimpleDelegator
8
10
  def initialize(schema: [])
9
- super(Fields.new(::Array.wrap(schema).map(&:with_indifferent_access)))
11
+ super(Fields.new(::Array.wrap(schema).map { |i| HashWithIndifferentAccess.wrap(i) }))
10
12
  end
11
13
 
12
14
  def trim(input)
13
- input.with_indifferent_access.keep_if { |property_name| includes_property?(property_name) }
15
+ HashWithIndifferentAccess.wrap(input).keep_if { |property_name| includes_property?(property_name) }
14
16
  end
15
17
 
16
18
  def apply(input, enforce_required:, &block)
17
- input.with_indifferent_access.tap do |input_with_indifferent_access|
19
+ HashWithIndifferentAccess.wrap(input).tap do |input_with_indifferent_access|
18
20
  apply_to_hash(self, input_with_indifferent_access, enforce_required: enforce_required, &block)
19
21
  end
20
22
  end
@@ -99,14 +101,14 @@ module Workato
99
101
  return Type::Time.from_date_time(value)
100
102
  when ::Date
101
103
  return value.to_date
102
- when ::Numeric, ::TrueClass, ::FalseClass, Workato::Extension::Binary, Type::UnicodeString,
103
- ::Array, ::Hash
104
+ when ::Numeric, ::TrueClass, ::FalseClass, Workato::Types::Binary, Type::UnicodeString,
105
+ ::Array, ::Hash, Stream::Proxy
104
106
  return value
105
107
  when Extension::Array::ArrayWhere
106
108
  return value.to_a
107
109
  when ::String
108
110
  if value.encoding == Encoding::ASCII_8BIT
109
- return Workato::Extension::Binary.new(value)
111
+ return Workato::Types::Binary.new(value)
110
112
  end
111
113
 
112
114
  return Type::UnicodeString.new(value)
@@ -117,7 +119,7 @@ module Workato
117
119
 
118
120
  if value.respond_to?(:read) && value.respond_to?(:rewind)
119
121
  value.rewind
120
- return Workato::Extension::Binary.new(value.read.force_encoding(Encoding::ASCII_8BIT))
122
+ return Workato::Types::Binary.new(value.read.force_encoding(Encoding::ASCII_8BIT))
121
123
  end
122
124
  end
123
125
 
@@ -176,7 +178,9 @@ module Workato
176
178
 
177
179
  def clean_values(field)
178
180
  field.transform_values! do |value|
179
- value.presence && (value.is_a?(::Symbol) && value.to_s || value)
181
+ next value if value.is_a?(FalseClass)
182
+
183
+ value.presence && ((value.is_a?(::Symbol) && value.to_s) || value)
180
184
  end
181
185
  field.compact!
182
186
  field
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'active_support/encrypted_configuration'
5
5
 
6
+ using Workato::Extension::HashWithIndifferentAccess
7
+
6
8
  module Workato
7
9
  module Connector
8
10
  module Sdk
@@ -98,13 +100,13 @@ module Workato
98
100
  end
99
101
 
100
102
  def read_encrypted_file
101
- all_settings = encrypted_configuration.config.with_indifferent_access
103
+ all_settings = HashWithIndifferentAccess.wrap(encrypted_configuration.config)
102
104
 
103
105
  (name ? all_settings.fetch(name) : all_settings) || {}
104
106
  end
105
107
 
106
108
  def update_encrypted_file(new_settings)
107
- all_settings = encrypted_configuration.config.with_indifferent_access
109
+ all_settings = HashWithIndifferentAccess.wrap(encrypted_configuration.config)
108
110
 
109
111
  merge_settings(all_settings, new_settings)
110
112
 
@@ -121,7 +123,7 @@ module Workato
121
123
  end
122
124
 
123
125
  def encrypted_configuration
124
- @encrypted_configuration ||= ActiveSupport::EncryptedConfiguration.new(
126
+ @encrypted_configuration ||= FixedEncryptedConfiguration.new(
125
127
  config_path: path,
126
128
  key_path: key_path || DEFAULT_MASTER_KEY_PATH,
127
129
  env_key: DEFAULT_MASTER_KEY_ENV,
@@ -133,6 +135,15 @@ module Workato
133
135
  YAML.dump(settings.to_hash)
134
136
  end
135
137
  end
138
+
139
+ class FixedEncryptedConfiguration < ActiveSupport::EncryptedConfiguration
140
+ private
141
+
142
+ def handle_missing_key
143
+ # Original methods incorectly passes constructor params
144
+ raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
145
+ end
146
+ end
136
147
  end
137
148
  end
138
149
  end