workato-connector-sdk 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -11
  3. data/lib/workato/cli/edit_command.rb +4 -3
  4. data/lib/workato/cli/exec_command.rb +27 -35
  5. data/lib/workato/cli/generate_command.rb +1 -0
  6. data/lib/workato/cli/generators/connector_generator.rb +1 -0
  7. data/lib/workato/cli/generators/master_key_generator.rb +1 -0
  8. data/lib/workato/cli/main.rb +44 -11
  9. data/lib/workato/cli/oauth2_command.rb +6 -5
  10. data/lib/workato/cli/push_command.rb +8 -5
  11. data/lib/workato/cli/schema_command.rb +6 -7
  12. data/lib/workato/connector/sdk/account_properties.rb +1 -0
  13. data/lib/workato/connector/sdk/action.rb +78 -20
  14. data/lib/workato/connector/sdk/block_invocation_refinements.rb +1 -0
  15. data/lib/workato/connector/sdk/connection.rb +204 -44
  16. data/lib/workato/connector/sdk/connector.rb +200 -65
  17. data/lib/workato/connector/sdk/dsl/account_property.rb +1 -0
  18. data/lib/workato/connector/sdk/dsl/aws.rb +23 -27
  19. data/lib/workato/connector/sdk/dsl/call.rb +6 -2
  20. data/lib/workato/connector/sdk/dsl/error.rb +1 -0
  21. data/lib/workato/connector/sdk/dsl/http.rb +2 -7
  22. data/lib/workato/connector/sdk/dsl/lookup_table.rb +1 -0
  23. data/lib/workato/connector/sdk/dsl/time.rb +6 -0
  24. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +38 -0
  25. data/lib/workato/connector/sdk/dsl/workato_schema.rb +1 -0
  26. data/lib/workato/connector/sdk/dsl.rb +19 -4
  27. data/lib/workato/connector/sdk/errors.rb +62 -4
  28. data/lib/workato/connector/sdk/lookup_tables.rb +1 -0
  29. data/lib/workato/connector/sdk/object_definitions.rb +22 -17
  30. data/lib/workato/connector/sdk/operation.rb +127 -88
  31. data/lib/workato/connector/sdk/request.rb +95 -31
  32. data/lib/workato/connector/sdk/schema/field/array.rb +1 -0
  33. data/lib/workato/connector/sdk/schema/field/convertors.rb +1 -0
  34. data/lib/workato/connector/sdk/schema/field/date.rb +1 -0
  35. data/lib/workato/connector/sdk/schema/field/date_time.rb +1 -0
  36. data/lib/workato/connector/sdk/schema/field/integer.rb +1 -0
  37. data/lib/workato/connector/sdk/schema/field/number.rb +1 -0
  38. data/lib/workato/connector/sdk/schema/field/object.rb +1 -0
  39. data/lib/workato/connector/sdk/schema/field/string.rb +1 -0
  40. data/lib/workato/connector/sdk/schema/type/time.rb +1 -0
  41. data/lib/workato/connector/sdk/schema/type/unicode_string.rb +1 -0
  42. data/lib/workato/connector/sdk/schema.rb +1 -0
  43. data/lib/workato/connector/sdk/settings.rb +9 -4
  44. data/lib/workato/connector/sdk/summarize.rb +3 -2
  45. data/lib/workato/connector/sdk/trigger.rb +106 -10
  46. data/lib/workato/connector/sdk/version.rb +2 -1
  47. data/lib/workato/connector/sdk/workato_schemas.rb +1 -0
  48. data/lib/workato/connector/sdk/xml.rb +1 -0
  49. data/lib/workato/connector/sdk.rb +8 -0
  50. data/lib/workato/extension/array.rb +1 -0
  51. data/lib/workato/extension/case_sensitive_headers.rb +1 -0
  52. data/lib/workato/extension/currency.rb +2 -1
  53. data/lib/workato/extension/date.rb +1 -0
  54. data/lib/workato/extension/enumerable.rb +1 -0
  55. data/lib/workato/extension/extra_chain_cert.rb +1 -0
  56. data/lib/workato/extension/hash.rb +1 -0
  57. data/lib/workato/extension/integer.rb +1 -0
  58. data/lib/workato/extension/nil_class.rb +1 -0
  59. data/lib/workato/extension/object.rb +1 -0
  60. data/lib/workato/extension/phone.rb +2 -1
  61. data/lib/workato/extension/string.rb +6 -2
  62. data/lib/workato/extension/symbol.rb +1 -0
  63. data/lib/workato/extension/time.rb +1 -0
  64. data/lib/workato/testing/vcr_encrypted_cassette_serializer.rb +5 -0
  65. data/lib/workato/testing/vcr_multipart_body_matcher.rb +1 -0
  66. data/lib/workato/utilities/encoding.rb +57 -0
  67. data/lib/workato/web/app.rb +1 -0
  68. data/lib/workato-connector-sdk.rb +1 -0
  69. metadata +88 -17
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './block_invocation_refinements'
@@ -27,20 +28,34 @@ module Workato
27
28
  def sleep(seconds)
28
29
  ::Kernel.sleep(seconds.presence || 0)
29
30
  end
31
+
32
+ def puts(*args)
33
+ T.unsafe(::Kernel).puts(*args)
34
+ end
30
35
  end
31
36
 
32
37
  class WithDsl
38
+ extend T::Sig
39
+
33
40
  include Global
34
41
 
35
42
  using BlockInvocationRefinements
36
43
 
37
- def execute(*args, &block)
38
- instance_exec(*args, &block)
44
+ sig { params(connection: Connection, args: T.untyped, block: T.untyped).returns(T.untyped) }
45
+ def execute(connection, *args, &block)
46
+ @connection = connection
47
+ T.unsafe(self).instance_exec(*args, &block)
39
48
  end
40
49
 
41
- def self.execute(*args, &block)
42
- WithDsl.new.execute(*args, &block)
50
+ sig { params(connection: Connection, args: T.untyped, block: T.untyped).returns(T.untyped) }
51
+ def self.execute(connection, *args, &block)
52
+ T.unsafe(WithDsl.new).execute(connection, *args, &block)
43
53
  end
54
+
55
+ private
56
+
57
+ sig { returns(Connection) }
58
+ attr_reader :connection
44
59
  end
45
60
  end
46
61
  end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Workato
@@ -5,16 +6,55 @@ module Workato
5
6
  module Sdk
6
7
  InvalidDefinitionError = Class.new(StandardError)
7
8
 
8
- 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)
9
30
 
10
31
  CustomRequestError = Class.new(StandardError)
11
32
 
12
33
  RuntimeError = Class.new(StandardError)
13
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
+
14
54
  class RequestError < StandardError
15
- attr_reader :method,
16
- :code,
17
- :response
55
+ attr_reader :method
56
+ attr_reader :code
57
+ attr_reader :response
18
58
 
19
59
  def initialize(message:, method:, code:, response:)
20
60
  super(message)
@@ -40,6 +80,24 @@ module Workato
40
80
  super(message)
41
81
  end
42
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)
43
101
  end
44
102
  end
45
103
  end
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'csv'
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './block_invocation_refinements'
@@ -8,36 +9,40 @@ module Workato
8
9
  class ObjectDefinitions
9
10
  using BlockInvocationRefinements
10
11
 
11
- def initialize(object_definitions:, connection:, methods:, settings:)
12
+ def initialize(object_definitions:, connection:, methods:)
12
13
  @object_definitions_source = object_definitions
13
14
  @methods_source = methods
14
15
  @connection = connection
15
- @settings = settings
16
16
  define_object_definition_methods(object_definitions)
17
17
  end
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
- settings: @settings
31
- ).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
32
37
  end
33
38
  end
34
39
 
35
40
  private
36
41
 
37
- attr_reader :methods_source,
38
- :connection,
39
- :settings,
40
- :object_definitions_source
42
+ attr_reader :methods_source
43
+ attr_reader :connection
44
+ attr_reader :settings
45
+ attr_reader :object_definitions_source
41
46
 
42
47
  def define_object_definition_methods(object_definitions)
43
48
  object_definitions.each do |(object, _definition)|
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './dsl'
@@ -7,7 +8,39 @@ require_relative './schema'
7
8
  module Workato
8
9
  module Connector
9
10
  module Sdk
11
+ module SorbetTypes
12
+ OperationInputHash = T.type_alias { T::Hash[T.any(Symbol, String), T.untyped] }
13
+
14
+ OperationExecuteProc = T.type_alias do
15
+ T.proc.params(
16
+ arg0: HashWithIndifferentAccess,
17
+ arg1: HashWithIndifferentAccess,
18
+ arg2: T.any(Schema, T::Array[HashWithIndifferentAccess]),
19
+ arg3: T.any(Schema, T::Array[HashWithIndifferentAccess]),
20
+ arg4: HashWithIndifferentAccess
21
+ ).returns(
22
+ T.untyped
23
+ )
24
+ end
25
+
26
+ OperationSchema = T.type_alias do
27
+ T.any(Schema, T::Array[T::Hash[T.any(Symbol, String), T.untyped]])
28
+ end
29
+
30
+ OperationSchemaProc = T.type_alias do
31
+ T.proc.params(
32
+ arg0: HashWithIndifferentAccess,
33
+ arg1: HashWithIndifferentAccess,
34
+ arg2: HashWithIndifferentAccess
35
+ ).returns(
36
+ T.nilable(T.any(SorbetTypes::OperationSchema, T::Hash[T.any(Symbol, String), T.untyped]))
37
+ )
38
+ end
39
+ end
40
+
10
41
  class Operation
42
+ extend T::Sig
43
+
11
44
  include Dsl::Global
12
45
  include Dsl::HTTP
13
46
  include Dsl::Call
@@ -15,21 +48,38 @@ module Workato
15
48
 
16
49
  using BlockInvocationRefinements
17
50
 
18
- cattr_accessor :on_settings_updated
19
-
20
- def initialize(connection:, operation: {}, methods: {}, settings: {}, object_definitions: nil)
21
- @connection = connection
22
- @settings = settings
23
- @operation = operation.with_indifferent_access
24
- @_methods = methods.with_indifferent_access
25
- @object_definitions = object_definitions
51
+ sig do
52
+ params(
53
+ operation: SorbetTypes::SourceHash,
54
+ methods: SorbetTypes::SourceHash,
55
+ connection: Connection,
56
+ object_definitions: T.nilable(ObjectDefinitions)
57
+ ).void
58
+ end
59
+ def initialize(operation: {}, methods: {}, connection: Connection.new, object_definitions: nil)
60
+ @operation = T.let(operation.with_indifferent_access, HashWithIndifferentAccess)
61
+ @_methods = T.let(methods.with_indifferent_access, HashWithIndifferentAccess)
62
+ @connection = T.let(connection, Connection)
63
+ @object_definitions = T.let(object_definitions, T.nilable(ObjectDefinitions))
64
+ end
65
+
66
+ sig do
67
+ params(
68
+ settings: T.nilable(SorbetTypes::SettingsHash),
69
+ input: SorbetTypes::OperationInputHash,
70
+ extended_input_schema: SorbetTypes::OperationSchema,
71
+ extended_output_schema: SorbetTypes::OperationSchema,
72
+ continue: T::Hash[T.any(Symbol, String), T.untyped],
73
+ block: SorbetTypes::OperationExecuteProc
74
+ ).returns(
75
+ T.untyped
76
+ )
26
77
  end
27
-
28
78
  def execute(settings = nil, input = {}, extended_input_schema = [], extended_output_schema = [], continue = {},
29
79
  &block)
30
- @settings = settings.with_indifferent_access if settings # is being used in request for refresh tokens
31
- request_or_result = instance_exec(
32
- @settings.with_indifferent_access, # a copy of settings hash is being used in executable blocks
80
+ connection.merge_settings!(settings) if settings
81
+ request_or_result = T.unsafe(self).instance_exec(
82
+ connection.settings,
33
83
  input.with_indifferent_access,
34
84
  Array.wrap(extended_input_schema).map(&:with_indifferent_access),
35
85
  Array.wrap(extended_output_schema).map(&:with_indifferent_access),
@@ -39,55 +89,78 @@ module Workato
39
89
  resolve_request(request_or_result)
40
90
  end
41
91
 
92
+ sig do
93
+ params(
94
+ settings: T.nilable(SorbetTypes::SettingsHash),
95
+ config_fields: SorbetTypes::OperationInputHash
96
+ ).returns(
97
+ HashWithIndifferentAccess
98
+ )
99
+ end
42
100
  def extended_schema(settings = nil, config_fields = {})
43
101
  object_definitions_hash = object_definitions.lazy(settings, config_fields)
44
102
  {
45
- input: schema_fields(object_definitions_hash, settings, config_fields, &operation[:input_fields]),
103
+ input: Array.wrap(
104
+ schema_fields(object_definitions_hash, settings, config_fields, &operation[:input_fields])
105
+ ),
46
106
  output: schema_fields(object_definitions_hash, settings, config_fields, &operation[:output_fields])
47
107
  }.with_indifferent_access
48
108
  end
49
109
 
110
+ sig do
111
+ params(
112
+ settings: T.nilable(SorbetTypes::SettingsHash),
113
+ config_fields: SorbetTypes::OperationInputHash
114
+ ).returns(
115
+ SorbetTypes::OperationSchema
116
+ )
117
+ end
50
118
  def input_fields(settings = nil, config_fields = {})
51
119
  object_definitions_hash = object_definitions.lazy(settings, config_fields)
52
- schema_fields(object_definitions_hash, settings, config_fields, &operation[:input_fields])
120
+ Array.wrap(schema_fields(object_definitions_hash, settings, config_fields, &operation[:input_fields]))
53
121
  end
54
122
 
123
+ sig do
124
+ params(
125
+ settings: T.nilable(SorbetTypes::SettingsHash),
126
+ config_fields: SorbetTypes::OperationInputHash
127
+ ).returns(
128
+ T.nilable(SorbetTypes::OperationSchema)
129
+ )
130
+ end
55
131
  def output_fields(settings = nil, config_fields = {})
56
132
  object_definitions_hash = object_definitions.lazy(settings, config_fields)
57
- schema_fields(object_definitions_hash, settings, config_fields, &operation[:output_fields])
133
+ T.cast(
134
+ schema_fields(object_definitions_hash, settings, config_fields, &operation[:output_fields]),
135
+ T.nilable(SorbetTypes::OperationSchema)
136
+ )
58
137
  end
59
138
 
139
+ sig { params(input: SorbetTypes::OperationInputHash).returns(T.untyped) }
60
140
  def summarize_input(input = {})
61
141
  summarize(input, operation[:summarize_input])
62
142
  end
63
143
 
144
+ sig { params(output: SorbetTypes::OperationInputHash).returns(T.untyped) }
64
145
  def summarize_output(output = {})
65
146
  summarize(output, operation[:summarize_output])
66
147
  end
67
148
 
149
+ sig do
150
+ params(
151
+ settings: T.nilable(SorbetTypes::SettingsHash),
152
+ input: SorbetTypes::OperationInputHash
153
+ ).returns(
154
+ T.untyped
155
+ )
156
+ end
68
157
  def sample_output(settings = nil, input = {})
69
158
  execute(settings, input, &operation[:sample_output])
70
159
  end
71
160
 
72
- def refresh_authorization!(http_code, http_body, exception, settings = {})
73
- return unless refresh_auth?(http_code, http_body, exception)
74
-
75
- new_settings = if /oauth2/i =~ connection.authorization.type
76
- refresh_oauth2_token(settings)
77
- elsif connection.authorization.acquire?
78
- acquire_token(settings)
79
- end
80
- return unless new_settings
81
-
82
- settings.merge!(new_settings)
83
-
84
- on_settings_updated&.call("Refresh token triggered on response \"#{exception}\"", settings)
85
-
86
- settings
87
- end
88
-
89
161
  private
90
162
 
163
+ sig { params(input: SorbetTypes::OperationInputHash, schema: Schema).returns(SorbetTypes::OperationInputHash) }
91
164
  def apply_input_schema(input, schema)
92
165
  input = schema.trim(input)
93
166
  schema.apply(input, enforce_required: true) do |value, field|
@@ -95,27 +168,40 @@ module Workato
95
168
  end
96
169
  end
97
170
 
171
+ sig { params(output: SorbetTypes::OperationInputHash, schema: Schema).returns(SorbetTypes::OperationInputHash) }
98
172
  def apply_output_schema(output, schema)
99
173
  schema.apply(output, enforce_required: false) do |value, field|
100
174
  field.parse_output(value, @_methods[field[:parse_output]])
101
175
  end
102
176
  end
103
177
 
178
+ sig { returns(SorbetTypes::OperationSchema) }
104
179
  def config_fields_schema
105
180
  operation[:config_fields] || []
106
181
  end
107
182
 
183
+ sig { params(data: SorbetTypes::OperationInputHash, paths: T::Array[String]).returns(T.untyped) }
108
184
  def summarize(data, paths)
109
185
  return data unless paths.present?
110
186
 
111
187
  Summarize.new(data: data, paths: paths).call
112
188
  end
113
189
 
190
+ sig do
191
+ params(
192
+ object_definitions_hash: HashWithIndifferentAccess,
193
+ settings: T.nilable(SorbetTypes::SettingsHash),
194
+ config_fields: SorbetTypes::OperationInputHash,
195
+ schema_proc: T.nilable(SorbetTypes::OperationSchemaProc)
196
+ ).returns(
197
+ T.nilable(T.any(SorbetTypes::OperationSchema, T::Hash[T.any(Symbol, String), T.untyped]))
198
+ )
199
+ end
114
200
  def schema_fields(object_definitions_hash, settings, config_fields, &schema_proc)
115
201
  return [] unless schema_proc
116
202
 
117
203
  execute(settings, config_fields) do |connection, input|
118
- instance_exec(
204
+ T.unsafe(self).instance_exec(
119
205
  object_definitions_hash,
120
206
  connection,
121
207
  input,
@@ -124,6 +210,7 @@ module Workato
124
210
  end
125
211
  end
126
212
 
213
+ sig { params(request_or_result: T.untyped).returns(T.untyped) }
127
214
  def resolve_request(request_or_result)
128
215
  case request_or_result
129
216
  when Request
@@ -151,64 +238,16 @@ module Workato
151
238
  end
152
239
  end
153
240
 
154
- def refresh_auth?(http_code, http_body, exception)
155
- refresh_on = connection.authorization.refresh_on
156
- refresh_on.blank? || refresh_on.any? do |pattern|
157
- pattern.is_a?(::Integer) && pattern == http_code ||
158
- pattern === exception&.to_s ||
159
- pattern === http_body
160
- end
161
- end
162
-
163
- def acquire_token(settings)
164
- connection.authorization.acquire(settings)
165
- end
166
-
167
- def refresh_oauth2_token_using_refresh(settings)
168
- new_tokens, new_settings = connection.authorization.refresh(settings, settings[:refresh_token])
169
- new_tokens.with_indifferent_access.merge(new_settings || {})
241
+ sig { returns(ObjectDefinitions) }
242
+ def object_definitions
243
+ T.must(@object_definitions)
170
244
  end
171
245
 
172
- def refresh_oauth2_token_using_token_url(settings)
173
- if settings[:refresh_token].blank?
174
- raise NotImplementedError, 'refresh_token is empty. ' \
175
- 'Use workato oauth2 command to acquire access_token and refresh_token'
176
- end
177
-
178
- response = RestClient::Request.execute(
179
- url: connection.authorization.token_url(settings),
180
- method: :post,
181
- payload: {
182
- client_id: connection.authorization.client_id(settings),
183
- client_secret: connection.authorization.client_secret(settings),
184
- grant_type: :refresh_token,
185
- refresh_token: settings[:refresh_token]
186
- },
187
- headers: {
188
- accept: :json
189
- }
190
- )
191
- tokens = JSON.parse(response.body)
192
- {
193
- access_token: tokens['access_token'],
194
- refresh_token: tokens['refresh_token'].presence || settings[:refresh_token]
195
- }.with_indifferent_access
196
- end
197
-
198
- def refresh_oauth2_token(settings)
199
- if connection.authorization.refresh?
200
- refresh_oauth2_token_using_refresh(settings)
201
- elsif connection.authorization.token_url?
202
- refresh_oauth2_token_using_token_url(settings)
203
- else
204
- raise InvalidDefinitionError, "'refresh' block or 'token_url' is required for refreshing the token"
205
- end
206
- end
246
+ sig { returns(HashWithIndifferentAccess) }
247
+ attr_reader :operation
207
248
 
208
- attr_reader :operation,
209
- :connection,
210
- :settings,
211
- :object_definitions
249
+ sig { returns(Connection) }
250
+ attr_reader :connection
212
251
  end
213
252
  end
214
253
  end