workato-connector-sdk 1.0.3 → 1.2.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 (33) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -0
  3. data/lib/workato/cli/exec_command.rb +31 -8
  4. data/lib/workato/cli/multi_auth_selected_fallback.rb +33 -0
  5. data/lib/workato/cli/oauth2_command.rb +50 -12
  6. data/lib/workato/connector/sdk/connection.rb +84 -14
  7. data/lib/workato/connector/sdk/connector.rb +6 -4
  8. data/lib/workato/connector/sdk/dsl/aws.rb +5 -2
  9. data/lib/workato/connector/sdk/dsl/call.rb +5 -2
  10. data/lib/workato/connector/sdk/dsl/csv.rb +125 -0
  11. data/lib/workato/connector/sdk/dsl/execution_context.rb +44 -0
  12. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +8 -1
  13. data/lib/workato/connector/sdk/dsl.rb +1 -0
  14. data/lib/workato/connector/sdk/errors.rb +69 -1
  15. data/lib/workato/connector/sdk/lookup_tables.rb +3 -1
  16. data/lib/workato/connector/sdk/object_definitions.rb +16 -10
  17. data/lib/workato/connector/sdk/operation.rb +24 -8
  18. data/lib/workato/connector/sdk/request.rb +136 -58
  19. data/lib/workato/connector/sdk/schema.rb +5 -3
  20. data/lib/workato/connector/sdk/settings.rb +4 -2
  21. data/lib/workato/connector/sdk/trigger.rb +42 -19
  22. data/lib/workato/connector/sdk/version.rb +1 -1
  23. data/lib/workato/connector/sdk.rb +3 -2
  24. data/lib/workato/extension/case_sensitive_headers.rb +0 -25
  25. data/lib/workato/extension/content_encoding_decoder.rb +67 -0
  26. data/lib/workato/extension/currency.rb +1 -1
  27. data/lib/workato/extension/extra_chain_cert.rb +0 -14
  28. data/lib/workato/extension/hash_with_indifferent_access.rb +19 -0
  29. data/lib/workato/extension/phone.rb +1 -1
  30. data/lib/workato/extension/string.rb +6 -2
  31. data/lib/workato/utilities/encoding.rb +57 -0
  32. data/lib/workato/{connector/sdk → utilities}/xml.rb +4 -4
  33. metadata +48 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4348a5f43c1aca0d39967b9b90c04b17d158a74c
4
- data.tar.gz: befa5442c5eca5641824e0e426cbec67c6a2dc77
2
+ SHA256:
3
+ metadata.gz: b49034b5762b2b44d0987d72eb74327e379284347b793714149d85d354a5bb57
4
+ data.tar.gz: 1be17d19381fe42689ef4a157a56eca0fb8c61a9c0e90e981e5a373305ed771f
5
5
  SHA512:
6
- metadata.gz: d74275e1a04b3a90059971dc048256ef3f720bbf09db590678353cc920e133ec4627a7870c5832663f1f793e9b8e47d2cfa19f3cdab81bd7c5abf2f0021c5db3
7
- data.tar.gz: ebbf0c161b5fcb932db95dac377408ab01b237bc1541a27e2e19c2b2cf9e2dbea60934a2cd1b37ebd56955c1d531e91656342d14904dbcdf66109fe99f7fe14f
6
+ metadata.gz: 5bcc365d25ec713df12b75d9d6d5fb8a6a73266b94cf012829e4a2c98af955f1fcfa7b0d0f7e69861a4ee6cb54af24ed8952bbb590553e93a37fcf2933c69459
7
+ data.tar.gz: 1da6193b9a1b04e3d3e2c6fb34fcd0eeec7666908c61df5c7ec386381ba8f33207b50b8ffc1e9d581fecb599fc423f84f9f79867120322356aa4d645b9b7da5e
data/README.md CHANGED
@@ -16,6 +16,7 @@ This guide below showcases how you can do the following things:
16
16
  2. Choose between Ruby versions `2.4.10` `2.5.X` or `2.7.X`. Our preferred version is `2.7.X`.
17
17
  3. Verify you're running a valid ruby version. Do this by running either `ruby -v` or the commands within your version manager. i.e., `rvm current` if you have installed RVM.
18
18
  4. For Windows you need tzinfo-data gem installed as well. `gem install tzinfo-data`
19
+ 5. SDK depends on `charlock_holmes` gem. Check [gem's documentation](https://github.com/brianmario/charlock_holmes#installing) if you have troubles when install this dependency. Additional [details for Windows](https://github.com/brianmario/charlock_holmes/issues/84#issuecomment-652877605)
19
20
 
20
21
  ```bash
21
22
  ruby -v
@@ -2,11 +2,13 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'thor'
5
+ require_relative './multi_auth_selected_fallback'
5
6
 
6
7
  module Workato
7
8
  module CLI
8
9
  class ExecCommand
9
10
  include Thor::Shell
11
+ include MultiAuthSelectedFallback
10
12
 
11
13
  DebugExceptionError = Class.new(StandardError)
12
14
 
@@ -63,7 +65,7 @@ module Workato
63
65
  oauth2_code: options[:oauth2_code],
64
66
  redirect_url: options[:redirect_url],
65
67
  refresh_token: options[:refresh_token],
66
- recipe_id: SecureRandom.uuid
68
+ recipe_id: Workato::Connector::Sdk::Operation.recipe_id!
67
69
  }
68
70
  end
69
71
 
@@ -84,13 +86,21 @@ module Workato
84
86
  )
85
87
  @settings = settings_store.read
86
88
 
89
+ Workato::Connector::Sdk::Connection.multi_auth_selected_fallback = lambda do |options|
90
+ next @selected_auth_type if @selected_auth_type
91
+
92
+ with_user_interaction do
93
+ @selected_auth_type = multi_auth_selected_fallback(options)
94
+ end
95
+ end
96
+
87
97
  Workato::Connector::Sdk::Connection.on_settings_update = lambda do |message, &refresher|
88
- begin
89
- $stdout.pause if verbose?
90
- say('')
91
- say(message)
92
- new_settings = refresher.call
98
+ new_settings = refresher.call
99
+ break unless new_settings
100
+
101
+ with_user_interaction do
93
102
  loop do
103
+ say(message)
94
104
  answer = ask('Updated settings file with new connection attributes? (Yes or No)').to_s.downcase
95
105
  break new_settings if %w[n no].include?(answer)
96
106
  next unless %w[y yes].include?(answer)
@@ -98,8 +108,6 @@ module Workato
98
108
  settings_store.update(new_settings)
99
109
  break new_settings
100
110
  end
101
- ensure
102
- $stdout.resume if verbose?
103
111
  end
104
112
  end
105
113
 
@@ -128,6 +136,11 @@ module Workato
128
136
 
129
137
  def execute_path
130
138
  connector.invoke(path, params)
139
+ rescue Workato::Connector::Sdk::InvalidMultiAuthDefinition => e
140
+ raise "#{e.message}. Please ensure:\n"\
141
+ "- 'selected' block is defined and returns value from 'options' list\n" \
142
+ "- settings file contains value expected by 'selected' block\n\n"\
143
+ 'See more: https://docs.workato.com/developing-connectors/sdk/guides/authentication/multi_auth.html'
131
144
  rescue Exception => e # rubocop:disable Lint/RescueException
132
145
  raise DebugExceptionError, e if options[:debug]
133
146
 
@@ -178,6 +191,16 @@ module Workato
178
191
  output
179
192
  end
180
193
 
194
+ def with_user_interaction
195
+ $stdout.pause if verbose?
196
+ say('')
197
+
198
+ yield
199
+ ensure
200
+ say('')
201
+ $stdout.resume if verbose?
202
+ end
203
+
181
204
  class ProgressLogger < SimpleDelegator
182
205
  def initialize(progress)
183
206
  super($stdout)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module CLI
5
+ module MultiAuthSelectedFallback
6
+ private
7
+
8
+ def multi_auth_selected_fallback(options)
9
+ say('Please select current auth type for multi-auth connector:')
10
+ options = options.keys
11
+ options.each_with_index do |option, idx|
12
+ say "[#{idx + 1}] #{option}"
13
+ end
14
+ say '[q] <exit>'
15
+ say('')
16
+
17
+ multi_auth_selected_fallback = loop do
18
+ answer = ask('Your choice:').to_s.downcase
19
+ break if answer == 'q'
20
+ next unless /\d+/ =~ answer && options[answer.to_i - 1]
21
+
22
+ break options[answer.to_i - 1]
23
+ end
24
+ return unless multi_auth_selected_fallback
25
+
26
+ say('')
27
+ say('Put selected auth type in your settings file to avoid this message in future')
28
+
29
+ multi_auth_selected_fallback
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,11 +3,13 @@
3
3
 
4
4
  require 'securerandom'
5
5
  require 'workato/web/app'
6
+ require_relative './multi_auth_selected_fallback'
6
7
 
7
8
  module Workato
8
9
  module CLI
9
10
  class OAuth2Command
10
11
  include Thor::Shell
12
+ include MultiAuthSelectedFallback
11
13
 
12
14
  AWAIT_CODE_TIMEOUT_INTERVAL = 180 # seconds
13
15
  AWAIT_CODE_SLEEP_INTERVAL = 5 # seconds
@@ -24,6 +26,7 @@ module Workato
24
26
  end
25
27
 
26
28
  def call
29
+ ensure_oauth2_type
27
30
  require_gems
28
31
  start_webrick
29
32
 
@@ -94,16 +97,31 @@ module Workato
94
97
  end
95
98
 
96
99
  def stop_webrick
100
+ return unless @thread
101
+
97
102
  Rack::Handler::WEBrick.shutdown
98
103
  @thread.exit
99
104
  end
100
105
 
106
+ def ensure_oauth2_type
107
+ unless connector.connection.authorization.oauth2?
108
+ raise 'Authorization type is not OAuth2. '\
109
+ 'For multi-auth connector ensure correct auth type was used. '\
110
+ "Expected: 'oauth2', got: '#{connector.connection.authorization.type}''"
111
+ end
112
+ rescue Workato::Connector::Sdk::InvalidMultiAuthDefinition => e
113
+ raise "#{e.message}. Please ensure:\n"\
114
+ "- 'selected' block is defined and returns value from 'options' list\n" \
115
+ "- settings file contains value expected by 'selected' block\n\n"\
116
+ 'See more: https://docs.workato.com/developing-connectors/sdk/guides/authentication/multi_auth.html'
117
+ end
118
+
101
119
  def client
102
120
  @client ||= OAuth2::Client.new(
103
- connector.connection.authorization.client_id(settings),
104
- connector.connection.authorization.client_secret(settings),
105
- site: connector.connection.base_uri(settings),
106
- token_url: connector.connection.authorization.token_url(settings),
121
+ connector.connection.authorization.client_id,
122
+ connector.connection.authorization.client_secret,
123
+ site: connector.connection.base_uri,
124
+ token_url: connector.connection.authorization.token_url,
107
125
  redirect_uri: redirect_url
108
126
  )
109
127
  end
@@ -112,14 +130,14 @@ module Workato
112
130
  return @authorize_url if defined?(@authorize_url)
113
131
 
114
132
  @authorize_url =
115
- if (authorization_url = connector.connection.authorization.authorization_url(settings))
133
+ if (authorization_url = connector.connection.authorization.authorization_url)
116
134
  params = {
117
135
  state: SecureRandom.hex(8),
118
- client_id: connector.connection.authorization.client_id(settings),
136
+ client_id: connector.connection.authorization.client_id,
119
137
  redirect_uri: redirect_url
120
- }
138
+ }.with_indifferent_access
121
139
  uri = URI(authorization_url)
122
- uri.query = params.with_indifferent_access.merge(Rack::Utils.parse_nested_query(uri.query || '')).to_param
140
+ uri.query = params.merge(Rack::Utils.parse_nested_query(uri.query || '')).to_param
123
141
  uri.to_s
124
142
  end
125
143
  end
@@ -133,12 +151,25 @@ module Workato
133
151
  end
134
152
 
135
153
  def settings
136
- @settings ||= settings_store.read
154
+ return @settings if defined?(@settings)
155
+
156
+ @settings = settings_store.read
157
+
158
+ Workato::Connector::Sdk::Connection.multi_auth_selected_fallback = lambda do |options|
159
+ next @selected_auth_type if @selected_auth_type
160
+
161
+ with_user_interaction do
162
+ @selected_auth_type = multi_auth_selected_fallback(options)
163
+ end
164
+ end
165
+
166
+ @settings
137
167
  end
138
168
 
139
169
  def connector
140
170
  @connector ||= Workato::Connector::Sdk::Connector.from_file(
141
- options[:connector] || Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH
171
+ options[:connector] || Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH,
172
+ settings
142
173
  )
143
174
  end
144
175
 
@@ -156,8 +187,8 @@ module Workato
156
187
  end
157
188
 
158
189
  def acquire_token(code)
159
- if connector.source.dig(:connection, :authorization, :acquire)
160
- tokens, _, extra_settings = connector.connection.authorization.acquire(settings, await_code, redirect_url)
190
+ if connector.connection.authorization.source[:acquire]
191
+ tokens, _, extra_settings = connector.connection.authorization.acquire(nil, code, redirect_url)
161
192
  tokens ||= {}
162
193
  extra_settings ||= {}
163
194
  extra_settings.merge(tokens)
@@ -178,6 +209,13 @@ module Workato
178
209
  response = http.request(request)
179
210
  response.body
180
211
  end
212
+
213
+ def with_user_interaction
214
+ say('')
215
+ yield
216
+ ensure
217
+ say('')
218
+ end
181
219
  end
182
220
  end
183
221
  end
@@ -3,9 +3,33 @@
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
11
35
 
@@ -15,6 +39,7 @@ module Workato
15
39
  attr_reader :source
16
40
 
17
41
  cattr_accessor :on_settings_update
42
+ cattr_accessor :multi_auth_selected_fallback
18
43
 
19
44
  sig do
20
45
  params(
@@ -24,8 +49,8 @@ module Workato
24
49
  ).void
25
50
  end
26
51
  def initialize(connection: {}, methods: {}, settings: {})
27
- @methods_source = T.let(methods.with_indifferent_access, HashWithIndifferentAccess)
28
- @source = T.let(connection.with_indifferent_access, HashWithIndifferentAccess)
52
+ @methods_source = T.let(HashWithIndifferentAccess.wrap(methods), HashWithIndifferentAccess)
53
+ @source = T.let(HashWithIndifferentAccess.wrap(connection), HashWithIndifferentAccess)
29
54
  @settings = T.let(settings, SorbetTypes::SettingsHash)
30
55
  end
31
56
 
@@ -37,6 +62,7 @@ module Workato
37
62
  sig { returns(HashWithIndifferentAccess) }
38
63
  def settings
39
64
  # we can't freeze or memoise because some developers modify it for storing something temporary in it.
65
+ # always return a new copy
40
66
  @settings.with_indifferent_access
41
67
  end
42
68
 
@@ -64,7 +90,10 @@ module Workato
64
90
 
65
91
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
66
92
  def base_uri(settings = nil)
67
- source[:base_uri]&.call(settings ? settings.with_indifferent_access.freeze : self.settings)
93
+ return unless source[:base_uri]
94
+
95
+ merge_settings!(settings) if settings
96
+ Dsl::WithDsl.execute(self, self.settings, &source[:base_uri])
68
97
  end
69
98
 
70
99
  sig do
@@ -101,9 +130,6 @@ module Workato
101
130
  class Authorization
102
131
  extend T::Sig
103
132
 
104
- sig { returns(HashWithIndifferentAccess) }
105
- attr_reader :source
106
-
107
133
  sig do
108
134
  params(
109
135
  connection: Connection,
@@ -123,6 +149,16 @@ module Workato
123
149
  (source[:type].presence || 'none').to_s
124
150
  end
125
151
 
152
+ sig { returns(T::Boolean) }
153
+ def oauth2?
154
+ !!(/oauth2/i =~ type)
155
+ end
156
+
157
+ sig { returns(T::Boolean) }
158
+ def multi?
159
+ @source[:type].to_s == 'multi'
160
+ end
161
+
126
162
  sig { returns(T::Array[T.any(String, Symbol, Regexp, Integer)]) }
127
163
  def refresh_on
128
164
  Array.wrap(source[:refresh_on]).compact
@@ -135,10 +171,10 @@ module Workato
135
171
 
136
172
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
137
173
  def client_id(settings = nil)
174
+ @connection.merge_settings!(settings) if settings
138
175
  client_id = source[:client_id]
139
176
 
140
177
  if client_id.is_a?(Proc)
141
- @connection.merge_settings!(settings) if settings
142
178
  Dsl::WithDsl.execute(@connection, @connection.settings, &client_id)
143
179
  else
144
180
  client_id
@@ -147,10 +183,10 @@ module Workato
147
183
 
148
184
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
149
185
  def client_secret(settings = nil)
186
+ @connection.merge_settings!(settings) if settings
150
187
  client_secret_source = source[:client_secret]
151
188
 
152
189
  if client_secret_source.is_a?(Proc)
153
- @connection.merge_settings!(settings) if settings
154
190
  Dsl::WithDsl.execute(@connection, @connection.settings, &client_secret_source)
155
191
  else
156
192
  client_secret_source
@@ -159,12 +195,18 @@ module Workato
159
195
 
160
196
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
161
197
  def authorization_url(settings = nil)
162
- source[:authorization_url]&.call(settings&.with_indifferent_access || @connection.settings)
198
+ @connection.merge_settings!(settings) if settings
199
+ return unless source[:authorization_url]
200
+
201
+ Dsl::WithDsl.execute(@connection, @connection.settings, &source[:authorization_url])
163
202
  end
164
203
 
165
204
  sig { params(settings: T.nilable(SorbetTypes::SettingsHash)).returns(T.nilable(String)) }
166
205
  def token_url(settings = nil)
167
- source[:token_url]&.call(settings&.with_indifferent_access || @connection.settings)
206
+ @connection.merge_settings!(settings) if settings
207
+ return unless source[:token_url]
208
+
209
+ Dsl::WithDsl.execute(@connection, @connection.settings, &source[:token_url])
168
210
  end
169
211
 
170
212
  sig do
@@ -172,9 +214,10 @@ module Workato
172
214
  settings: T.nilable(SorbetTypes::SettingsHash),
173
215
  oauth2_code: T.nilable(String),
174
216
  redirect_url: T.nilable(String)
175
- ).returns(HashWithIndifferentAccess)
217
+ ).returns(T.nilable(SorbetTypes::AcquireOutput))
176
218
  end
177
219
  def acquire(settings = nil, oauth2_code = nil, redirect_url = nil)
220
+ @connection.merge_settings!(settings) if settings
178
221
  acquire_proc = source[:acquire]
179
222
  raise InvalidDefinitionError, "Expect 'acquire' block" unless acquire_proc
180
223
 
@@ -202,6 +245,8 @@ module Workato
202
245
  ).returns(T::Boolean)
203
246
  end
204
247
  def refresh?(http_code, http_body, exception)
248
+ return false unless oauth2? || source[:acquire].present?
249
+
205
250
  refresh_on = self.refresh_on
206
251
  refresh_on.blank? || refresh_on.any? do |pattern|
207
252
  pattern.is_a?(::Integer) && pattern == http_code ||
@@ -212,10 +257,10 @@ module Workato
212
257
 
213
258
  sig { params(settings: HashWithIndifferentAccess).returns(T.nilable(HashWithIndifferentAccess)) }
214
259
  def refresh!(settings)
215
- if /oauth2/i =~ type
260
+ if oauth2?
216
261
  refresh_oauth2_token(settings)
217
262
  elsif source[:acquire].present?
218
- acquire(settings)
263
+ T.cast(acquire(settings), T.nilable(HashWithIndifferentAccess))
219
264
  end
220
265
  end
221
266
 
@@ -228,6 +273,7 @@ module Workato
228
273
  )
229
274
  end
230
275
  def refresh(settings = nil, refresh_token = nil)
276
+ @connection.merge_settings!(settings) if settings
231
277
  refresh_proc = source[:refresh]
232
278
  raise InvalidDefinitionError, "Expect 'refresh' block" unless refresh_proc
233
279
 
@@ -242,6 +288,27 @@ module Workato
242
288
  end
243
289
  end
244
290
 
291
+ sig { returns(HashWithIndifferentAccess) }
292
+ def source
293
+ return @source unless multi?
294
+
295
+ unless @source[:selected]
296
+ raise InvalidMultiAuthDefinition, "Multi-auth connection must define 'selected' block"
297
+ end
298
+
299
+ if @source[:options].blank?
300
+ raise InvalidMultiAuthDefinition, "Multi-auth connection must define 'options' list"
301
+ end
302
+
303
+ selected_auth_key = @source[:selected].call(@connection.settings)
304
+ selected_auth_key ||= @connection.multi_auth_selected_fallback&.call(@source[:options])
305
+ selected_auth_value = @source.dig(:options, selected_auth_key)
306
+
307
+ raise UnresolvedMultiAuthOptionError, selected_auth_key unless selected_auth_value
308
+
309
+ selected_auth_value
310
+ end
311
+
245
312
  private
246
313
 
247
314
  sig { returns(HashWithIndifferentAccess) }
@@ -264,7 +331,10 @@ module Workato
264
331
  sig { params(settings: HashWithIndifferentAccess).returns(HashWithIndifferentAccess) }
265
332
  def refresh_oauth2_token_using_refresh(settings)
266
333
  new_tokens, new_settings = refresh(settings, settings[:refresh_token])
267
- new_tokens.with_indifferent_access.merge(new_settings || {})
334
+ new_tokens = HashWithIndifferentAccess.wrap(new_tokens)
335
+ return new_tokens unless new_settings
336
+
337
+ new_tokens.merge(new_settings)
268
338
  end
269
339
 
270
340
  sig { params(settings: HashWithIndifferentAccess).returns(HashWithIndifferentAccess) }
@@ -1,6 +1,8 @@
1
1
  # typed: strict
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
@@ -21,10 +23,10 @@ module Workato
21
23
 
22
24
  sig { params(definition: SorbetTypes::SourceHash, settings: SorbetTypes::SettingsHash).void }
23
25
  def initialize(definition, settings = {})
24
- @source = T.let(definition.with_indifferent_access, HashWithIndifferentAccess)
25
- @settings = T.let(settings.with_indifferent_access, HashWithIndifferentAccess)
26
- @connection_source = T.let(@source[:connection] || {}.with_indifferent_access, HashWithIndifferentAccess)
27
- @methods_source = T.let(@source[:methods] || {}.with_indifferent_access, HashWithIndifferentAccess)
26
+ @source = T.let(HashWithIndifferentAccess.wrap(definition), HashWithIndifferentAccess)
27
+ @settings = T.let(HashWithIndifferentAccess.wrap(settings), HashWithIndifferentAccess)
28
+ @connection_source = T.let(HashWithIndifferentAccess.wrap(@source[:connection]), HashWithIndifferentAccess)
29
+ @methods_source = T.let(HashWithIndifferentAccess.wrap(@source[:methods]), HashWithIndifferentAccess)
28
30
  end
29
31
 
30
32
  sig { params(path: String, params: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
@@ -2,6 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'aws-sigv4'
5
+ require 'workato/utilities/xml'
6
+
7
+ using Workato::Extension::HashWithIndifferentAccess
5
8
 
6
9
  module Workato
7
10
  module Connector
@@ -55,7 +58,7 @@ module Workato
55
58
  method: method,
56
59
  path: path,
57
60
  params: params,
58
- headers: (headers || {}).with_indifferent_access,
61
+ headers: HashWithIndifferentAccess.wrap(headers),
59
62
  payload: payload
60
63
  )
61
64
 
@@ -134,7 +137,7 @@ module Workato
134
137
  headers: headers,
135
138
  method: :get
136
139
  )
137
- response = Workato::Connector::Sdk::Xml.parse_xml_to_hash(response.body)
140
+ response = Workato::Utilities::Xml.parse_xml_to_hash(response.body)
138
141
 
139
142
  temp_credentials = response.dig('AssumeRoleResponse', 0, 'AssumeRoleResult', 0, 'Credentials', 0)
140
143
  {
@@ -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
@@ -0,0 +1,125 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'csv'
5
+
6
+ module Workato
7
+ module Connector
8
+ module Sdk
9
+ CsvError = Class.new(Sdk::RuntimeError)
10
+
11
+ CsvFormatError = Class.new(CsvError)
12
+
13
+ class CsvFileTooBigError < CsvError
14
+ extend T::Sig
15
+
16
+ sig { returns(Integer) }
17
+ attr_reader :size
18
+
19
+ sig { returns(Integer) }
20
+ attr_reader :max
21
+
22
+ sig { params(size: Integer, max: Integer).void }
23
+ def initialize(size, max)
24
+ super("CSV file is too big. Max allowed: #{max.to_s(:human_size)}, got: #{size.to_s(:human_size)}")
25
+ @size = T.let(size, Integer)
26
+ @max = T.let(max, Integer)
27
+ end
28
+ end
29
+
30
+ class CsvFileTooManyLinesError < CsvError
31
+ extend T::Sig
32
+
33
+ sig { returns(Integer) }
34
+ attr_reader :max
35
+
36
+ sig { params(max: Integer).void }
37
+ def initialize(max)
38
+ super("CSV file has too many lines. Max allowed: #{max}")
39
+ @max = T.let(max, Integer)
40
+ end
41
+ end
42
+
43
+ module Dsl
44
+ module Csv
45
+ extend T::Sig
46
+
47
+ MAX_FILE_SIZE_FOR_PARSE = T.let(30.megabytes, Integer)
48
+ private_constant :MAX_FILE_SIZE_FOR_PARSE
49
+
50
+ MAX_LINES_FOR_PARSE = 65_000
51
+ private_constant :MAX_LINES_FOR_PARSE
52
+
53
+ class << self
54
+ extend T::Sig
55
+
56
+ sig do
57
+ params(
58
+ str: String,
59
+ headers: T.any(T::Boolean, T::Array[String], String),
60
+ col_sep: T.nilable(String),
61
+ row_sep: T.nilable(String),
62
+ quote_char: T.nilable(String),
63
+ skip_blanks: T.nilable(T::Boolean),
64
+ skip_first_line: T::Boolean
65
+ ).returns(
66
+ T::Array[T::Hash[String, T.untyped]]
67
+ )
68
+ end
69
+ def parse(str, headers:, col_sep: nil, row_sep: nil, quote_char: nil, skip_blanks: nil,
70
+ skip_first_line: false)
71
+ if str.bytesize > MAX_FILE_SIZE_FOR_PARSE
72
+ raise CsvFileTooBigError.new(str.bytesize, MAX_FILE_SIZE_FOR_PARSE)
73
+ end
74
+
75
+ index = 0
76
+ options = { col_sep: col_sep, row_sep: row_sep, quote_char: quote_char, headers: headers,
77
+ skip_blanks: skip_blanks }.compact
78
+ Enumerator.new do |consumer|
79
+ CSV.parse(str, options) do |row|
80
+ if index.zero? && skip_first_line
81
+ index += 1
82
+ next
83
+ end
84
+ if index == MAX_LINES_FOR_PARSE
85
+ raise CsvFileTooManyLinesError, MAX_LINES_FOR_PARSE
86
+ end
87
+
88
+ index += 1
89
+ consumer.yield(T.cast(row, CSV::Row).to_hash)
90
+ end
91
+ end.to_a
92
+ rescue CSV::MalformedCSVError => e
93
+ raise CsvFormatError, e
94
+ rescue ArgumentError => e
95
+ raise Sdk::RuntimeError, e.message
96
+ end
97
+
98
+ sig do
99
+ params(
100
+ str: T.nilable(String),
101
+ headers: T.nilable(T::Array[String]),
102
+ col_sep: T.nilable(String),
103
+ row_sep: T.nilable(String),
104
+ quote_char: T.nilable(String),
105
+ force_quotes: T.nilable(T::Boolean),
106
+ blk: T.proc.params(csv: CSV).void
107
+ ).returns(
108
+ String
109
+ )
110
+ end
111
+ def generate(str = nil, headers: nil, col_sep: nil, row_sep: nil, quote_char: nil, force_quotes: nil, &blk)
112
+ options = { col_sep: col_sep, row_sep: row_sep, quote_char: quote_char, headers: headers,
113
+ force_quotes: force_quotes }.compact
114
+ options[:write_headers] = options[:headers].present?
115
+
116
+ ::CSV.generate(str || String.new, options, &blk)
117
+ rescue ArgumentError => e
118
+ raise Sdk::RuntimeError, e.message
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end