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
@@ -11,29 +11,34 @@ module Workato
11
11
  extend T::Sig
12
12
  using BlockInvocationRefinements
13
13
 
14
+ include Dsl::ReinvokeAfter
15
+
14
16
  RETRY_DEFAULT_CODES = T.let([429, 500, 502, 503, 504, 507].freeze, T::Array[Integer])
15
17
  RETRY_DEFAULT_METHODS = T.let(%i[get head].freeze, T::Array[Symbol])
16
18
  RETRY_DELAY = T.let(5, Integer) # seconds
19
+ RETRY_DELAY_EXP_BASE = T.let(2, Integer)
17
20
  MAX_RETRIES = 3
18
21
 
19
- MAX_REINVOKES = 5
20
-
21
22
  sig do
22
23
  params(
23
24
  action: SorbetTypes::SourceHash,
24
25
  methods: SorbetTypes::SourceHash,
25
26
  connection: Connection,
26
- object_definitions: T.nilable(ObjectDefinitions)
27
+ object_definitions: T.nilable(ObjectDefinitions),
28
+ streams: Streams
27
29
  ).void
28
30
  end
29
- def initialize(action:, methods: {}, connection: Connection.new, object_definitions: nil)
31
+ def initialize(action:, methods: {}, connection: Connection.new, object_definitions: nil,
32
+ streams: ProhibitedStreams.new)
30
33
  super(
31
34
  operation: action,
32
35
  connection: connection,
33
36
  methods: methods,
34
- object_definitions: object_definitions
37
+ object_definitions: object_definitions,
38
+ streams: streams
35
39
  )
36
40
 
41
+ @retry_delay_factor = T.let(1, Integer)
37
42
  @retries_left = T.let(0, Integer)
38
43
  @retry_codes = T.let([], T::Array[Integer])
39
44
  @retry_methods = T.let([], T::Array[String])
@@ -58,33 +63,21 @@ module Workato
58
63
  &block)
59
64
  raise InvalidDefinitionError, "'execute' block is required for action" unless block || action[:execute]
60
65
 
61
- loop do
62
- if @reinvokes_remaining&.zero?
63
- raise "Max number of reinvokes on SDK Gem reached. Current limit is #{reinvoke_limit}"
64
- end
65
-
66
- reinvoke_sleep if @reinvoke_after
67
-
68
- reinvoke_reset
69
-
70
- result = super(
66
+ loop_reinvoke_after(continue) do |next_continue|
67
+ return super(
71
68
  settings,
72
69
  input,
73
70
  extended_input_schema,
74
71
  extended_output_schema,
75
- continue,
72
+ next_continue,
76
73
  &(block || action[:execute])
77
74
  )
78
-
79
- break result unless @reinvoke_after
80
-
81
- continue = @reinvoke_after.continue
82
75
  end
83
- rescue RequestError => e
76
+ rescue RequestFailedError => e
84
77
  raise e unless retry?(e)
85
78
 
86
79
  @retries_left -= 1
87
- sleep(RETRY_DELAY)
80
+ retry_sleep
88
81
  retry
89
82
  end
90
83
 
@@ -100,32 +93,6 @@ module Workato
100
93
  apply_output_schema(output, output_schema)
101
94
  end
102
95
 
103
- sig do
104
- params(
105
- continue: T::Hash[T.untyped, T.untyped],
106
- temp_output: T.nilable(T::Hash[T.untyped, T.untyped])
107
- ).void
108
- end
109
- def checkpoint!(continue:, temp_output: nil)
110
- # no-op
111
- end
112
-
113
- sig do
114
- params(
115
- seconds: Integer,
116
- continue: T::Hash[T.untyped, T.untyped],
117
- temp_output: T.nilable(T::Hash[T.untyped, T.untyped])
118
- ).void
119
- end
120
- def reinvoke_after(seconds:, continue:, temp_output: nil) # rubocop:disable Lint/UnusedMethodArgument
121
- @reinvokes_remaining = T.let(@reinvokes_remaining, T.nilable(Integer))
122
- @reinvokes_remaining = (@reinvokes_remaining ? @reinvokes_remaining - 1 : reinvoke_limit)
123
- @reinvoke_after = ReinvokeAfter.new(
124
- seconds: seconds,
125
- continue: continue
126
- )
127
- end
128
-
129
96
  private
130
97
 
131
98
  sig { returns(T::Array[T.any(Symbol, String, Regexp, Integer)]) }
@@ -150,10 +117,10 @@ module Workato
150
117
  retry_on_response.each { |m| m.is_a?(::Integer) ? @retry_codes << m : @retry_matchers << m }
151
118
  @retry_codes = RETRY_DEFAULT_CODES if @retry_codes.empty?
152
119
  @retry_methods = (retry_on_request.presence || RETRY_DEFAULT_METHODS).map(&:to_s).map(&:downcase)
153
- @retries_left = [[max_retries.is_a?(::Integer) && max_retries || MAX_RETRIES, MAX_RETRIES].min, 0].max
120
+ @retries_left = [[(max_retries.is_a?(::Integer) && max_retries) || MAX_RETRIES, MAX_RETRIES].min, 0].max
154
121
  end
155
122
 
156
- sig { params(exception: RequestError).returns(T::Boolean) }
123
+ sig { params(exception: RequestFailedError).returns(T::Boolean) }
157
124
  def retry?(exception)
158
125
  return false unless @retries_left.positive?
159
126
  return false unless @retry_codes.include?(exception.code.to_i)
@@ -165,28 +132,11 @@ module Workato
165
132
  end
166
133
 
167
134
  sig { void }
168
- def reinvoke_sleep
169
- sleep((ENV['WAIT_REINVOKE_AFTER'].presence || T.must(@reinvoke_after).seconds).to_f)
170
- end
171
-
172
- sig { returns(Integer) }
173
- def reinvoke_limit
174
- @reinvoke_limit = T.let(@reinvoke_limit, T.nilable(Integer))
175
- @reinvoke_limit ||= (ENV['MAX_REINVOKES'].presence || MAX_REINVOKES).to_i
176
- end
177
-
178
- sig { void }
179
- def reinvoke_reset
180
- @reinvoke_after = T.let(nil, T.nilable(ReinvokeAfter))
135
+ def retry_sleep
136
+ sleep(@retry_delay_factor * RETRY_DELAY)
137
+ @retry_delay_factor *= RETRY_DELAY_EXP_BASE
181
138
  end
182
139
 
183
- class ReinvokeAfter < T::Struct
184
- prop :seconds, T.any(Float, Integer)
185
- prop :continue, T::Hash[T.untyped, T.untyped]
186
- end
187
-
188
- private_constant :ReinvokeAfter
189
-
190
140
  alias action operation
191
141
  end
192
142
  end
@@ -6,25 +6,17 @@ module Workato
6
6
  module Sdk
7
7
  # match proc's arguments, even if it's a lambda.
8
8
  module BlockInvocationRefinements
9
- module CallRefinement
9
+ refine Proc do
10
10
  def call(*args, &block)
11
11
  super(*args.take(parameters.length), &block)
12
12
  end
13
13
  end
14
14
 
15
- refine Proc do
16
- prepend CallRefinement
17
- end
18
-
19
- module InstanceExecRefinement
15
+ refine BasicObject do
20
16
  def instance_exec(*args, &block)
21
17
  super(*args.take(block.parameters.length), &block)
22
18
  end
23
19
  end
24
-
25
- refine BasicObject do
26
- prepend InstanceExecRefinement
27
- end
28
20
  end
29
21
  end
30
22
  end
@@ -3,18 +3,44 @@
3
3
 
4
4
  require_relative './block_invocation_refinements'
5
5
 
6
+ using Workato::Extension::HashWithIndifferentAccess
7
+
6
8
  module Workato
7
9
  module Connector
8
10
  module Sdk
11
+ module SorbetTypes
12
+ AcquireOutput = T.type_alias do
13
+ T.any(
14
+ # oauth2
15
+ [
16
+ HashWithIndifferentAccess, # tokens
17
+ T.untyped, # resource_owner_id
18
+ T.nilable(HashWithIndifferentAccess) # settings
19
+ ],
20
+ [
21
+ HashWithIndifferentAccess, # tokens
22
+ T.untyped # resource_owner_id
23
+ ],
24
+ [
25
+ HashWithIndifferentAccess # tokens
26
+ ],
27
+ # custom_auth
28
+ HashWithIndifferentAccess
29
+ )
30
+ end
31
+ end
32
+
9
33
  class Connection
10
34
  extend T::Sig
35
+ include MonitorMixin
11
36
 
12
37
  using BlockInvocationRefinements
13
38
 
14
39
  sig { returns(HashWithIndifferentAccess) }
15
40
  attr_reader :source
16
41
 
17
- cattr_accessor :on_settings_update
42
+ class_attribute :on_settings_update, instance_predicate: false
43
+ class_attribute :multi_auth_selected_fallback, instance_predicate: false
18
44
 
19
45
  sig do
20
46
  params(
@@ -24,8 +50,9 @@ module Workato
24
50
  ).void
25
51
  end
26
52
  def initialize(connection: {}, methods: {}, settings: {})
27
- @methods_source = T.let(methods.with_indifferent_access, HashWithIndifferentAccess)
28
- @source = T.let(connection.with_indifferent_access, HashWithIndifferentAccess)
53
+ super()
54
+ @methods_source = T.let(HashWithIndifferentAccess.wrap(methods), HashWithIndifferentAccess)
55
+ @source = T.let(HashWithIndifferentAccess.wrap(connection), HashWithIndifferentAccess)
29
56
  @settings = T.let(settings, SorbetTypes::SettingsHash)
30
57
  end
31
58
 
@@ -37,7 +64,10 @@ module Workato
37
64
  sig { returns(HashWithIndifferentAccess) }
38
65
  def settings
39
66
  # we can't freeze or memoise because some developers modify it for storing something temporary in it.
40
- @settings.with_indifferent_access
67
+ # always return a new copy
68
+ synchronize do
69
+ @settings.with_indifferent_access
70
+ end
41
71
  end
42
72
 
43
73
  sig { params(settings: SorbetTypes::SettingsHash).returns(SorbetTypes::SettingsHash) }
@@ -64,16 +94,20 @@ module Workato
64
94
 
65
95
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
66
96
  def base_uri(settings = nil)
67
- source[:base_uri]&.call(settings ? settings.with_indifferent_access.freeze : self.settings)
97
+ return unless source[:base_uri]
98
+
99
+ merge_settings!(settings) if settings
100
+ global_dsl_context.execute(self.settings, &source['base_uri'])
68
101
  end
69
102
 
70
103
  sig do
71
104
  params(
72
105
  message: String,
106
+ settings_before: SorbetTypes::SettingsHash,
73
107
  refresher: T.proc.returns(T.nilable(SorbetTypes::SettingsHash))
74
108
  ).returns(T::Boolean)
75
109
  end
76
- def update_settings!(message, &refresher)
110
+ def update_settings!(message, settings_before, &refresher)
77
111
  updater = lambda do
78
112
  new_settings = refresher.call
79
113
  next unless new_settings
@@ -81,16 +115,18 @@ module Workato
81
115
  settings.merge(new_settings)
82
116
  end
83
117
 
84
- new_settings = if on_settings_update
85
- on_settings_update.call(message, &updater)
86
- else
87
- updater.call
88
- end
89
- return false unless new_settings
118
+ synchronize do
119
+ new_settings = if on_settings_update
120
+ T.must(on_settings_update).call(message, settings_before, updater)
121
+ else
122
+ updater.call
123
+ end
124
+ return false unless new_settings
90
125
 
91
- merge_settings!(new_settings)
126
+ merge_settings!(new_settings)
92
127
 
93
- true
128
+ true
129
+ end
94
130
  end
95
131
 
96
132
  private
@@ -98,12 +134,14 @@ module Workato
98
134
  sig { returns(HashWithIndifferentAccess) }
99
135
  attr_reader :methods_source
100
136
 
137
+ sig { returns(Dsl::WithDsl) }
138
+ def global_dsl_context
139
+ Dsl::WithDsl.new(self)
140
+ end
141
+
101
142
  class Authorization
102
143
  extend T::Sig
103
144
 
104
- sig { returns(HashWithIndifferentAccess) }
105
- attr_reader :source
106
-
107
145
  sig do
108
146
  params(
109
147
  connection: Connection,
@@ -123,6 +161,16 @@ module Workato
123
161
  (source[:type].presence || 'none').to_s
124
162
  end
125
163
 
164
+ sig { returns(T::Boolean) }
165
+ def oauth2?
166
+ !!(/oauth2/i =~ type)
167
+ end
168
+
169
+ sig { returns(T::Boolean) }
170
+ def multi?
171
+ @source[:type].to_s == 'multi'
172
+ end
173
+
126
174
  sig { returns(T::Array[T.any(String, Symbol, Regexp, Integer)]) }
127
175
  def refresh_on
128
176
  Array.wrap(source[:refresh_on]).compact
@@ -135,11 +183,11 @@ module Workato
135
183
 
136
184
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
137
185
  def client_id(settings = nil)
186
+ @connection.merge_settings!(settings) if settings
138
187
  client_id = source[:client_id]
139
188
 
140
189
  if client_id.is_a?(Proc)
141
- @connection.merge_settings!(settings) if settings
142
- Dsl::WithDsl.execute(@connection, @connection.settings, &client_id)
190
+ global_dsl_context.execute(@connection.settings, &client_id)
143
191
  else
144
192
  client_id
145
193
  end
@@ -147,11 +195,11 @@ module Workato
147
195
 
148
196
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
149
197
  def client_secret(settings = nil)
198
+ @connection.merge_settings!(settings) if settings
150
199
  client_secret_source = source[:client_secret]
151
200
 
152
201
  if client_secret_source.is_a?(Proc)
153
- @connection.merge_settings!(settings) if settings
154
- Dsl::WithDsl.execute(@connection, @connection.settings, &client_secret_source)
202
+ global_dsl_context.execute(@connection.settings, &client_secret_source)
155
203
  else
156
204
  client_secret_source
157
205
  end
@@ -159,12 +207,18 @@ module Workato
159
207
 
160
208
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
161
209
  def authorization_url(settings = nil)
162
- source[:authorization_url]&.call(settings&.with_indifferent_access || @connection.settings)
210
+ @connection.merge_settings!(settings) if settings
211
+ return unless source[:authorization_url]
212
+
213
+ global_dsl_context.execute(@connection.settings, &source[:authorization_url])
163
214
  end
164
215
 
165
216
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
166
217
  def token_url(settings = nil)
167
- source[:token_url]&.call(settings&.with_indifferent_access || @connection.settings)
218
+ @connection.merge_settings!(settings) if settings
219
+ return unless source[:token_url]
220
+
221
+ global_dsl_context.execute(@connection.settings, &source[:token_url])
168
222
  end
169
223
 
170
224
  sig do
@@ -172,9 +226,10 @@ module Workato
172
226
  settings: T.nilable(SorbetTypes::SettingsHash),
173
227
  oauth2_code: T.nilable(String),
174
228
  redirect_url: T.nilable(String)
175
- ).returns(HashWithIndifferentAccess)
229
+ ).returns(T.nilable(SorbetTypes::AcquireOutput))
176
230
  end
177
231
  def acquire(settings = nil, oauth2_code = nil, redirect_url = nil)
232
+ @connection.merge_settings!(settings) if settings
178
233
  acquire_proc = source[:acquire]
179
234
  raise InvalidDefinitionError, "Expect 'acquire' block" unless acquire_proc
180
235
 
@@ -202,11 +257,11 @@ module Workato
202
257
  ).returns(T::Boolean)
203
258
  end
204
259
  def refresh?(http_code, http_body, exception)
205
- return false unless /oauth2/i =~ type || source[:acquire].present?
260
+ return false unless oauth2? || source[:acquire].present?
206
261
 
207
262
  refresh_on = self.refresh_on
208
263
  refresh_on.blank? || refresh_on.any? do |pattern|
209
- pattern.is_a?(::Integer) && pattern == http_code ||
264
+ (pattern.is_a?(::Integer) && pattern == http_code) ||
210
265
  pattern === exception&.to_s ||
211
266
  pattern === http_body
212
267
  end
@@ -214,10 +269,10 @@ module Workato
214
269
 
215
270
  sig { params(settings: HashWithIndifferentAccess).returns(T.nilable(HashWithIndifferentAccess)) }
216
271
  def refresh!(settings)
217
- if /oauth2/i =~ type
272
+ if oauth2?
218
273
  refresh_oauth2_token(settings)
219
274
  elsif source[:acquire].present?
220
- acquire(settings)
275
+ T.cast(acquire(settings), T.nilable(HashWithIndifferentAccess))
221
276
  end
222
277
  end
223
278
 
@@ -230,6 +285,7 @@ module Workato
230
285
  )
231
286
  end
232
287
  def refresh(settings = nil, refresh_token = nil)
288
+ @connection.merge_settings!(settings) if settings
233
289
  refresh_proc = source[:refresh]
234
290
  raise InvalidDefinitionError, "Expect 'refresh' block" unless refresh_proc
235
291
 
@@ -244,6 +300,27 @@ module Workato
244
300
  end
245
301
  end
246
302
 
303
+ sig { returns(HashWithIndifferentAccess) }
304
+ def source
305
+ return @source unless multi?
306
+
307
+ unless @source[:selected]
308
+ raise InvalidMultiAuthDefinition, "Multi-auth connection must define 'selected' block"
309
+ end
310
+
311
+ if @source[:options].blank?
312
+ raise InvalidMultiAuthDefinition, "Multi-auth connection must define 'options' list"
313
+ end
314
+
315
+ selected_auth_key = @source[:selected].call(@connection.settings)
316
+ selected_auth_key ||= @connection.multi_auth_selected_fallback&.call(@source[:options])
317
+ selected_auth_value = @source.dig(:options, selected_auth_key)
318
+
319
+ raise UnresolvedMultiAuthOptionError, selected_auth_key unless selected_auth_value
320
+
321
+ selected_auth_value
322
+ end
323
+
247
324
  private
248
325
 
249
326
  sig { returns(HashWithIndifferentAccess) }
@@ -266,14 +343,17 @@ module Workato
266
343
  sig { params(settings: HashWithIndifferentAccess).returns(HashWithIndifferentAccess) }
267
344
  def refresh_oauth2_token_using_refresh(settings)
268
345
  new_tokens, new_settings = refresh(settings, settings[:refresh_token])
269
- new_tokens.with_indifferent_access.merge(new_settings || {})
346
+ new_tokens = HashWithIndifferentAccess.wrap(new_tokens)
347
+ return new_tokens unless new_settings
348
+
349
+ new_tokens.merge(new_settings)
270
350
  end
271
351
 
272
352
  sig { params(settings: HashWithIndifferentAccess).returns(HashWithIndifferentAccess) }
273
353
  def refresh_oauth2_token_using_token_url(settings)
274
354
  if settings[:refresh_token].blank?
275
355
  raise NotImplementedError, 'refresh_token is empty. ' \
276
- 'Use workato oauth2 command to acquire access_token and refresh_token'
356
+ 'Use workato oauth2 command to acquire access_token and refresh_token'
277
357
  end
278
358
 
279
359
  response = RestClient::Request.execute(
@@ -295,6 +375,11 @@ module Workato
295
375
  refresh_token: tokens['refresh_token'].presence || settings[:refresh_token]
296
376
  }.with_indifferent_access
297
377
  end
378
+
379
+ sig { returns(Dsl::WithDsl) }
380
+ def global_dsl_context
381
+ Dsl::WithDsl.new(@connection)
382
+ end
298
383
  end
299
384
 
300
385
  private_constant :Authorization