workato-connector-sdk 0.2.0 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1b501416ca8344b57fb05ebc9b48b3f1b19a4f4e
4
- data.tar.gz: 47666c7e297ead213ca1228c38d3ed15dfbd699d
2
+ SHA256:
3
+ metadata.gz: b958407bc907daf631e2bd91ecb5c002f911906e3bf8a88c00aedd612fe80bbe
4
+ data.tar.gz: 1445969d82d648667e1de62809722625d06e40a30bb4bec565f56f1b7ce113da
5
5
  SHA512:
6
- metadata.gz: 266afec14bd2e2b5dcb4104df330d9cb8f96bac11a1d8d6e9199ca3bbd0576c35ff35475ef7b661610cdeef9292d36c4091cd744cac095a8d067a42dc2ccd7da
7
- data.tar.gz: c1c1437158f46a43ab50102ce55d2846f76310d417fc130ca8d6aae1c0e34b7d962f26687929466cb94eb1859e5ab82d596c5eab736dd08e87ff0ae2dfdeeef7
6
+ metadata.gz: 4ad8b4ef4ed273ad65573acb5a4e45dd1e0772b24a4de82cc4c40a3fe0a926c8f93f067eab0df2a6f23b0f94317f2d4bcaad588d203f9323417d7a973084a1fe
7
+ data.tar.gz: 879463eea7f3e0b9ef01ce1a5019eafbc8af18610306541ff97f6b62520869e0db8d1de1c0a61b9bb3c233c0932dc780e1ec04fd61704350ece80e9fbf9c764d
data/exe/workato CHANGED
@@ -2,4 +2,15 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'workato/cli/main'
5
- Workato::CLI::Main.start
5
+
6
+ begin
7
+ Workato::CLI::Main.start
8
+ rescue Workato::CLI::ExecCommand::DebugExceptionError => e
9
+ raise e.cause
10
+ rescue SystemExit, SignalException
11
+ raise
12
+ rescue Exception => e # rubocop:disable Lint/RescueException
13
+ puts ''
14
+ puts e.message
15
+ exit(false)
16
+ end
@@ -28,7 +28,7 @@ module Workato
28
28
 
29
29
  puts 'File encrypted and saved.'
30
30
  rescue ActiveSupport::MessageEncryptor::InvalidMessage
31
- puts "Couldn't decrypt #{encrypted_file_path}. Perhaps you passed the wrong key?"
31
+ raise "Couldn't decrypt #{encrypted_file_path}. Perhaps you passed the wrong key?"
32
32
  end
33
33
 
34
34
  private
@@ -63,8 +63,6 @@ module Workato
63
63
  yield
64
64
  rescue Interrupt
65
65
  puts 'Aborted changing file: nothing saved.'
66
- rescue ActiveSupport::EncryptedFile::MissingKeyError => e
67
- puts e.message
68
66
  end
69
67
  end
70
68
  end
@@ -7,6 +7,8 @@ module Workato
7
7
  class ExecCommand
8
8
  include Thor::Shell
9
9
 
10
+ DebugExceptionError = Class.new(StandardError)
11
+
10
12
  def initialize(path:, options:)
11
13
  @path = path
12
14
  @options = options
@@ -122,16 +124,25 @@ module Workato
122
124
  methods = path.split('.')
123
125
  method = methods.pop
124
126
  object = methods.inject(params[:connector]) { |obj, m| obj.public_send(m) }
127
+ output = invoke_method(object, method)
128
+ if output.respond_to?(:invoke)
129
+ invoke_method(output, :invoke)
130
+ else
131
+ output
132
+ end
133
+ rescue Exception => e # rubocop:disable Lint/RescueException
134
+ raise DebugExceptionError, e if options[:debug]
135
+
136
+ raise
137
+ end
138
+
139
+ def invoke_method(object, method)
125
140
  parameters = object.method(method).parameters.reject { |p| p[0] == :block }.map(&:second)
126
141
  args = params.values_at(*parameters)
127
142
  if parameters.last == :args
128
143
  args = args.take(args.length - 1) + Array.wrap(args.last).flatten(1)
129
144
  end
130
145
  object.public_send(method, *args)
131
- rescue Exception => e # rubocop:disable Lint/RescueException
132
- raise e if options[:debug]
133
-
134
- e.message
135
146
  end
136
147
 
137
148
  def show_output(output)
@@ -70,8 +70,7 @@ module Workato
70
70
  def ensure_connector_source
71
71
  return if connector
72
72
 
73
- error "Can't find connector source code"
74
- exit
73
+ raise "Can't find connector source code"
75
74
  end
76
75
 
77
76
  def create_spec_file(type, name)
@@ -32,7 +32,7 @@ module Workato
32
32
 
33
33
  say_status :success, "Open #{authorize_url} in browser"
34
34
  Launchy.open(authorize_url) do |exception|
35
- raise(Error, "Attempted to open #{authorize_url} and failed because #{exception}")
35
+ raise "Attempted to open #{authorize_url} and failed because #{exception}"
36
36
  end
37
37
 
38
38
  code = await_code
@@ -45,11 +45,9 @@ module Workato
45
45
  settings_store.update(tokens)
46
46
  say_status :success, 'Update settings file'
47
47
  rescue Timeout::Error
48
- say "Have not received callback from OAuth2 provider in #{AWAIT_CODE_TIMEOUT_INTERVAL} seconds. Aborting!"
48
+ raise "Have not received callback from OAuth2 provider in #{AWAIT_CODE_TIMEOUT_INTERVAL} seconds. Aborting!"
49
49
  rescue Errno::EADDRINUSE
50
- say "Port #{port} already in use. Try to use different port with --port=#{rand(10_000..65_664)}"
51
- rescue StandardError => e
52
- say e.message
50
+ raise "Port #{port} already in use. Try to use different port with --port=#{rand(10_000..65_664)}"
53
51
  ensure
54
52
  stop_webrick
55
53
  end
@@ -49,13 +49,9 @@ module Workato
49
49
  say_status :waiting, 'Process package' if verbose?
50
50
 
51
51
  result = await_import(import_id)
52
- if result.fetch('status') == 'failed'
53
- say result.fetch('error').gsub("#{PACKAGE_ENTRY_NAME}.json: ", '')
54
- else
55
- say "Connector was successfully uploaded to #{api_base_url}"
56
- end
57
- rescue StandardError => e
58
- say e.message
52
+ raise human_friendly_error(result) if result.fetch('status') == 'failed'
53
+
54
+ say "Connector was successfully uploaded to #{api_base_url}"
59
55
  ensure
60
56
  FileUtils.rm_f(zip_file_path) if zip_file_path
61
57
  end
@@ -208,6 +204,10 @@ module Workato
208
204
  end
209
205
  end
210
206
 
207
+ def human_friendly_error(result)
208
+ result.fetch('error').gsub("#{PACKAGE_ENTRY_NAME}.json: ", '')
209
+ end
210
+
211
211
  private_constant :IMPORT_IN_PROGRESS,
212
212
  :API_USER_PATH,
213
213
  :API_IMPORT_PATH,
@@ -61,6 +61,17 @@ module Workato
61
61
  sleep(RETRY_DELAY) && retry
62
62
  end
63
63
 
64
+ def invoke(input = {})
65
+ extended_schema = extended_schema(nil, input)
66
+ config_schema = Schema.new(schema: config_fields_schema)
67
+ input_schema = Schema.new(schema: extended_schema[:input])
68
+ output_schema = Schema.new(schema: extended_schema[:output])
69
+
70
+ input = apply_input_schema(input, config_schema + input_schema)
71
+ output = execute(nil, input, input_schema, output_schema)
72
+ apply_output_schema(output, output_schema)
73
+ end
74
+
64
75
  def checkpoint!(continue:, temp_output: nil)
65
76
  # no-op
66
77
  end
@@ -115,7 +115,7 @@ module Workato
115
115
 
116
116
  def define_action_methods(actions)
117
117
  actions.each do |action, definition|
118
- define_singleton_method(action) do
118
+ define_singleton_method(action) do |input_ = nil|
119
119
  @actions ||= {}
120
120
  @actions[action] ||= Action.new(
121
121
  action: definition,
@@ -124,6 +124,9 @@ module Workato
124
124
  connection: connection,
125
125
  settings: settings
126
126
  )
127
+ return @actions[action] if input_.nil?
128
+
129
+ @actions[action].invoke(input_)
127
130
  end
128
131
  end
129
132
  end
@@ -221,7 +224,7 @@ module Workato
221
224
 
222
225
  def define_trigger_methods(triggers)
223
226
  triggers.each do |trigger, definition|
224
- define_singleton_method(trigger) do
227
+ define_singleton_method(trigger) do |input_ = nil, payload = {}, headers = {}, params = {}|
225
228
  @triggers[trigger] ||= Trigger.new(
226
229
  trigger: definition,
227
230
  object_definitions: object_definitions,
@@ -229,6 +232,10 @@ module Workato
229
232
  connection: connection,
230
233
  settings: settings
231
234
  )
235
+
236
+ return @triggers[trigger] if input_.nil?
237
+
238
+ @triggers[trigger].invoke(input_, payload, headers, params)
232
239
  end
233
240
  end
234
241
  end
@@ -63,7 +63,7 @@ module Workato
63
63
  raise "The requested length or random bytes sequence should be <= #{RANDOM_SIZE}"
64
64
  end
65
65
 
66
- String::Binary.new(::OpenSSL::Random.random_bytes(len))
66
+ Extension::Binary.new(::OpenSSL::Random.random_bytes(len))
67
67
  end
68
68
 
69
69
  ALLOWED_KEY_SIZES = [128, 192, 256].freeze
@@ -78,7 +78,7 @@ module Workato
78
78
  cipher.encrypt
79
79
  cipher.key = key
80
80
  cipher.iv = init_vector if init_vector.present?
81
- String::Binary.new(cipher.update(string) + cipher.final)
81
+ Extension::Binary.new(cipher.update(string) + cipher.final)
82
82
  end
83
83
 
84
84
  def aes_cbc_decrypt(string, key, init_vector = nil)
@@ -91,11 +91,11 @@ module Workato
91
91
  cipher.decrypt
92
92
  cipher.key = key
93
93
  cipher.iv = init_vector if init_vector.present?
94
- String::Binary.new(cipher.update(string) + cipher.final)
94
+ Extension::Binary.new(cipher.update(string) + cipher.final)
95
95
  end
96
96
 
97
97
  def pbkdf2_hmac_sha1(string, salt, iterations = 1000, key_len = 16)
98
- String::Binary.new(::OpenSSL::PKCS5.pbkdf2_hmac_sha1(string, salt, iterations, key_len))
98
+ Extension::Binary.new(::OpenSSL::PKCS5.pbkdf2_hmac_sha1(string, salt, iterations, key_len))
99
99
  end
100
100
  end
101
101
  end
@@ -5,6 +5,8 @@ module Workato
5
5
  module Sdk
6
6
  InvalidDefinitionError = Class.new(StandardError)
7
7
 
8
+ InvalidSchemaError = Class.new(StandardError)
9
+
8
10
  CustomRequestError = Class.new(StandardError)
9
11
 
10
12
  class RequestError < StandardError
@@ -25,6 +27,17 @@ module Workato
25
27
  super
26
28
  end
27
29
  end
30
+
31
+ class MissingRequiredInput < StandardError
32
+ def initialize(label, toggle_label)
33
+ message = if toggle_label && label != toggle_label
34
+ "Either '#{label}' or '#{toggle_label}' must be present"
35
+ else
36
+ "'#{label}' must be present"
37
+ end
38
+ super(message)
39
+ end
40
+ end
28
41
  end
29
42
  end
30
43
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative './dsl'
4
4
  require_relative './block_invocation_refinements'
5
+ require_relative './schema'
5
6
 
6
7
  module Workato
7
8
  module Connector
@@ -18,7 +19,7 @@ module Workato
18
19
 
19
20
  def initialize(connection:, operation: {}, methods: {}, settings: {}, object_definitions: nil)
20
21
  @connection = connection
21
- @settings = settings.with_indifferent_access
22
+ @settings = settings
22
23
  @operation = operation.with_indifferent_access
23
24
  @_methods = methods.with_indifferent_access
24
25
  @object_definitions = object_definitions
@@ -87,6 +88,23 @@ module Workato
87
88
 
88
89
  private
89
90
 
91
+ def apply_input_schema(input, schema)
92
+ input = schema.trim(input)
93
+ schema.apply(input, enforce_required: true) do |value, field|
94
+ field.render_input(value, @_methods[field[:render_input]])
95
+ end
96
+ end
97
+
98
+ def apply_output_schema(output, schema)
99
+ schema.apply(output, enforce_required: false) do |value, field|
100
+ field.parse_output(value, @_methods[field[:parse_output]])
101
+ end
102
+ end
103
+
104
+ def config_fields_schema
105
+ operation[:config_fields] || []
106
+ end
107
+
90
108
  def summarize(data, paths)
91
109
  return data unless paths.present?
92
110
 
@@ -94,7 +112,7 @@ module Workato
94
112
  end
95
113
 
96
114
  def schema_fields(object_definitions_hash, settings, config_fields, &schema_proc)
97
- return {} unless schema_proc
115
+ return [] unless schema_proc
98
116
 
99
117
  execute(settings, config_fields) do |connection, input|
100
118
  instance_exec(
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class Array < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'array'
15
+ }.with_indifferent_access.freeze
16
+
17
+ def initialize(field)
18
+ super(DEFAULT_ATTRIBUTES.merge(field))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ class Schema
7
+ module Field
8
+ module Convertors
9
+ def render_input(value, custom_convertor = nil)
10
+ apply_convertor(value, self[:render_input], custom_convertor)
11
+ end
12
+
13
+ def parse_output(value, custom_convertor = nil)
14
+ apply_convertor(value, self[:parse_output], custom_convertor)
15
+ end
16
+
17
+ private
18
+
19
+ def apply_convertor(value, builtin_convertor, custom_convertor)
20
+ return value unless builtin_convertor || custom_convertor
21
+ return send(builtin_convertor, value) if builtin_convertor && respond_to?(builtin_convertor, true)
22
+ return custom_convertor.call(value) if custom_convertor.is_a?(Proc)
23
+
24
+ raise ArgumentError, "Cannot find converter '#{builtin_convertor}'."
25
+ end
26
+
27
+ def integer_conversion(value)
28
+ value.try(:is_number?) && value.to_i || value
29
+ end
30
+
31
+ def boolean_conversion(value)
32
+ value.try(:is_true?)
33
+ end
34
+
35
+ def float_conversion(value)
36
+ value.try(:is_number?) && value.to_f || value
37
+ end
38
+
39
+ def item_array_wrap(items)
40
+ ::Array.wrap(items).presence&.flatten(1)
41
+ end
42
+
43
+ def date_conversion(value)
44
+ parse_date(value)
45
+ end
46
+
47
+ def date_iso8601_conversion(value)
48
+ parse_date(value)&.iso8601
49
+ end
50
+
51
+ def date_time_conversion(value)
52
+ parse_date_time(value)
53
+ end
54
+
55
+ def date_time_iso8601_conversion(value)
56
+ parse_date_time(value)&.iso8601
57
+ end
58
+
59
+ def convert_to_datetime(value)
60
+ value.try(:to_datetime) || value
61
+ end
62
+
63
+ def convert_to_datetime_wo_tz(value)
64
+ value.try(:to_datetime).strftime('%Y-%m-%d %H:%M:%S') || value
65
+ end
66
+
67
+ def parse_date_output(value)
68
+ value.try(:to_date) || value
69
+ end
70
+
71
+ def render_date_input(value)
72
+ try_in_time_zone(value).try(:to_date)
73
+ end
74
+
75
+ def convert_date_time(value)
76
+ try_in_time_zone(value)
77
+ end
78
+
79
+ def parse_date_time_epoch_millis(value)
80
+ if value.is_a?(::Time)
81
+ value
82
+ else
83
+ value.is_a?(Numeric) && ::Time.at(value.to_f / 1000)
84
+ end
85
+ end
86
+
87
+ def render_date_time_epoch_millis(value)
88
+ value.try(:to_f).try(:*, 1000).try(:to_i)
89
+ end
90
+
91
+ def parse_iso8601_timestamp(value)
92
+ if value.is_a?(::Time)
93
+ value
94
+ else
95
+ value.try(:to_time)
96
+ end
97
+ end
98
+
99
+ def render_iso8601_timestamp(value)
100
+ value.try(:to_time).try(:iso8601)
101
+ end
102
+
103
+ def parse_iso8601_date(value)
104
+ if value.is_a?(::Date)
105
+ value
106
+ else
107
+ value.try(:to_date)
108
+ end
109
+ end
110
+
111
+ def render_iso8601_date(value)
112
+ value.try(:to_date).try(:iso8601)
113
+ end
114
+
115
+ def parse_epoch_time(value)
116
+ if value.is_a?(::Time)
117
+ value
118
+ else
119
+ (value.is_a?(Numeric).presence || value.try(:is_number?).presence) && ::Time.zone.at(value.to_i)
120
+ end
121
+ end
122
+
123
+ def render_epoch_time(value)
124
+ value.try(:to_time).try(:to_i)
125
+ end
126
+
127
+ def parse_float_epoch_time(value)
128
+ if value.is_a?(::Time)
129
+ value
130
+ else
131
+ (value.is_a?(Numeric) || value.try(:is_number?)) && ::Time.zone.at(value.to_f)
132
+ end
133
+ end
134
+
135
+ def render_float_epoch_time(value)
136
+ value.try(:to_time).try(:to_f)
137
+ end
138
+
139
+ def implicit_utc_time(value)
140
+ value&.in_time_zone('UTC')
141
+ end
142
+
143
+ def implicit_utc_iso8601_time(value)
144
+ value&.in_time_zone('UTC')&.iso8601
145
+ end
146
+
147
+ # Helpers
148
+ #
149
+ def try_in_time_zone(value)
150
+ value.try(:in_time_zone, local_time_zone || ::Time.zone) || value
151
+ end
152
+
153
+ def local_time_zone
154
+ ENV['WORKATO_TIME_ZONE'] || Workato::Connector::Sdk::DEFAULT_TIME_ZONE
155
+ end
156
+
157
+ def parse_date(value)
158
+ if value.blank? || value.is_a?(::Date)
159
+ value.presence
160
+ elsif value.is_a?(::Time)
161
+ value.to_date
162
+ else
163
+ parse_time_string(value).to_date
164
+ end
165
+ end
166
+
167
+ def parse_date_time(value)
168
+ if value.blank? || value.is_a?(::Time)
169
+ value.presence
170
+ elsif value.is_a?(::Date)
171
+ value.in_time_zone(local_time_zone)
172
+ else
173
+ parse_time_string(value)
174
+ end
175
+ end
176
+
177
+ def parse_time_string(value)
178
+ value_time = ::Time.parse(value)
179
+ user_time = ActiveSupport::TimeZone[local_time_zone].parse(value)
180
+
181
+ # equal means value had its own offset/TZ or defaulted to system TZ with same offset as user's.
182
+ value_time == user_time ? value_time : user_time
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class Date < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'date_time',
15
+ control_type: 'date',
16
+ render_input: 'date_conversion',
17
+ parse_output: 'date_conversion'
18
+ }.with_indifferent_access.freeze
19
+
20
+ def initialize(field)
21
+ super(DEFAULT_ATTRIBUTES.merge(field))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class DateTime < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'date_time',
15
+ control_type: 'date_time',
16
+ render_input: 'date_time_conversion',
17
+ parse_output: 'date_time_conversion'
18
+ }.with_indifferent_access.freeze
19
+
20
+ def initialize(field)
21
+ super(DEFAULT_ATTRIBUTES.merge(field))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class Integer < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'integer',
15
+ control_type: 'number',
16
+ parse_output: 'integer_conversion'
17
+ }.with_indifferent_access.freeze
18
+
19
+ def initialize(field)
20
+ super(DEFAULT_ATTRIBUTES.merge(field))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class Number < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'number',
15
+ control_type: 'number',
16
+ parse_output: 'float_conversion'
17
+ }.with_indifferent_access.freeze
18
+
19
+ def initialize(field)
20
+ super(DEFAULT_ATTRIBUTES.merge(field))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class Object < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'object'
15
+ }.with_indifferent_access.freeze
16
+
17
+ def initialize(field)
18
+ super(DEFAULT_ATTRIBUTES.merge(field))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './convertors'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Field
10
+ class String < SimpleDelegator
11
+ include Convertors
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ type: 'string',
15
+ control_type: 'text'
16
+ }.with_indifferent_access.freeze
17
+
18
+ def initialize(field)
19
+ super(DEFAULT_ATTRIBUTES.merge(field))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Schema
9
+ module Type
10
+ class Time < ::Time
11
+ PRECISION = 6
12
+
13
+ def to_s(*args)
14
+ if args.present?
15
+ super
16
+ else
17
+ xmlschema(PRECISION)
18
+ end
19
+ end
20
+
21
+ def self.from_time(value)
22
+ new(
23
+ value.year,
24
+ value.month,
25
+ value.day,
26
+ value.hour,
27
+ value.min,
28
+ value.sec + Rational(value.nsec, 1_000_000_000),
29
+ value.utc_offset
30
+ )
31
+ end
32
+
33
+ def self.from_date_time(value)
34
+ new(
35
+ value.year,
36
+ value.month,
37
+ value.day,
38
+ value.hour,
39
+ value.min,
40
+ value.sec + Rational(value.nsec, 1_000_000_000),
41
+ value.zone
42
+ )
43
+ end
44
+
45
+ def self.xmlschema(str)
46
+ from_time(super(str))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ class Schema
7
+ module Type
8
+ class UnicodeString < ::String
9
+ def initialize(str)
10
+ super(str, {})
11
+ encode!('UTF-8')
12
+ end
13
+
14
+ def binary?
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ class Schema < SimpleDelegator
7
+ def initialize(schema: [])
8
+ super(Fields.new(::Array.wrap(schema).map(&:with_indifferent_access)))
9
+ end
10
+
11
+ def trim(input)
12
+ input.with_indifferent_access.keep_if { |property_name| includes_property?(property_name) }
13
+ end
14
+
15
+ def apply(input, enforce_required:, &block)
16
+ input.with_indifferent_access.tap do |input_with_indifferent_access|
17
+ apply_to_hash(self, input_with_indifferent_access, enforce_required: enforce_required, &block)
18
+ end
19
+ end
20
+
21
+ def +(other)
22
+ if other.is_a?(Schema)
23
+ Schema.new.tap do |schema|
24
+ schema.__setobj__(__getobj__ + other.__getobj__)
25
+ end
26
+ else
27
+ Schema.new(schema: __getobj__ + other)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def includes_property?(name)
34
+ find_property_by_name(name).present?
35
+ end
36
+
37
+ def find_property_by_name(name)
38
+ find do |property|
39
+ (property[:name].to_s == name.to_s) || (property.dig(:toggle_field, :name).to_s == name.to_s)
40
+ end
41
+ end
42
+
43
+ def apply_to_hash(properties, object, enforce_required: false, &block)
44
+ return if properties.blank? || object.nil?
45
+
46
+ properties.each do |property|
47
+ apply_to_value(property, object, property[:name], object[property[:name]], &block)
48
+ if (toggle_property = property[:toggle_field])
49
+ apply_to_value(toggle_property, object, toggle_property[:name], object[toggle_property[:name]], &block)
50
+ end
51
+
52
+ next unless enforce_required
53
+ next if optional_or_present?(property, object) || optional_or_present?(property[:toggle_field], object)
54
+
55
+ raise MissingRequiredInput.new(property[:label], property.dig(:toggle_field, :label))
56
+ end
57
+ end
58
+
59
+ def optional_or_present?(property, object)
60
+ property.present? && (
61
+ property[:optional] ||
62
+ property[:runtime_optional] ||
63
+ (value = object[property[:name]]).present? ||
64
+ value.is_a?(FalseClass) ||
65
+ (value.is_a?(::String) && !value.empty?)
66
+ )
67
+ end
68
+
69
+ def apply_to_array(property, array, &block)
70
+ array.each_with_index do |item, index|
71
+ apply_to_value(property, array, index, item, &block)
72
+ end
73
+ end
74
+
75
+ def apply_to_value(property, container, index, value, &block)
76
+ return unless property.present? && value.present?
77
+
78
+ if value.respond_to?(:each_key)
79
+ apply_to_hash(property[:properties], value, &block)
80
+ elsif value.respond_to?(:each_with_index)
81
+ apply_to_array(property, value, &block)
82
+ end
83
+
84
+ container[index] = if !value.nil? && block
85
+ normalize_value(yield(value, property))
86
+ else
87
+ normalize_value(value)
88
+ end
89
+ end
90
+
91
+ def normalize_value(value)
92
+ return value if value.blank?
93
+
94
+ case value
95
+ when ::Time
96
+ return Type::Time.from_time(value)
97
+ when ::DateTime
98
+ return Type::Time.from_date_time(value)
99
+ when ::Date
100
+ return value.to_date
101
+ when ::Numeric, ::TrueClass, ::FalseClass, Workato::Extension::Binary, Type::UnicodeString,
102
+ ::Array, ::Hash
103
+ return value
104
+ when Extension::Array::ArrayWhere
105
+ return value.to_a
106
+ when ::String
107
+ if value.encoding == Encoding::ASCII_8BIT
108
+ return Workato::Extension::Binary.new(value)
109
+ end
110
+
111
+ return Type::UnicodeString.new(value)
112
+ else
113
+ if value.respond_to?(:to_time)
114
+ return Type::Time.from_time(value.to_time)
115
+ end
116
+
117
+ if value.respond_to?(:read) && value.respond_to?(:rewind)
118
+ value.rewind
119
+ return Workato::Extension::Binary.new(value.read.force_encoding(Encoding::ASCII_8BIT))
120
+ end
121
+ end
122
+
123
+ raise ArgumentError, "Unsupported data type: #{value.class}"
124
+ end
125
+ end
126
+
127
+ class Fields < ::Array
128
+ def initialize(fields)
129
+ ::Array.wrap(fields).each do |field|
130
+ field = prepare_attributes(field)
131
+ self << field_with_defaults(field)
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def prepare_attributes(field)
138
+ if (render_input = field.delete(:convert_input) || field[:render_input])
139
+ field[:render_input] = render_input.is_a?(Proc) ? nil : render_input
140
+ end
141
+ if (parse_output = field.delete(:convert_output) || field[:parse_output])
142
+ field[:parse_output] = parse_output.is_a?(Proc) ? nil : parse_output
143
+ end
144
+ field[:optional] = true unless field.key?(:optional)
145
+ field[:label] ||= field[:name].labelize
146
+
147
+ clean_values(field)
148
+
149
+ if (toggle_field = field[:toggle_field]).present?
150
+ raise InvalidSchemaError, 'toggle_hint not present' if field[:toggle_hint].blank?
151
+
152
+ unless toggle_field[:name].present? && toggle_field[:type].present?
153
+ raise InvalidSchemaError, 'toggle_field not complete'
154
+ end
155
+
156
+ if toggle_field[:optional].present? && (toggle_field[:optional] != field[:optional])
157
+ raise InvalidSchemaError, 'toggle field cannot change optional attribute'
158
+ end
159
+
160
+ field[:toggle_field] = field_with_defaults(field[:toggle_field]).tap do |tg_field|
161
+ tg_field.except!(:render_input, :parse_output, :control_type)
162
+ tg_field[:control_type] = toggle_field[:control_type]
163
+ clean_values(tg_field)
164
+ end
165
+ end
166
+
167
+ if field[:control_type].try(:start_with?, 'small-')
168
+ field[:control_type].remove!(/^small-/)
169
+ elsif field[:control_type].try(:start_with?, 'medium-')
170
+ field[:control_type].remove!(/^medium-/)
171
+ end
172
+
173
+ field
174
+ end
175
+
176
+ def clean_values(field)
177
+ field.transform_values! do |value|
178
+ value.presence && (value.is_a?(::Symbol) && value.to_s || value)
179
+ end
180
+ field.compact!
181
+ field
182
+ end
183
+
184
+ def field_with_defaults(field)
185
+ type = field.delete(:type).to_s
186
+
187
+ case type
188
+ when 'integer'
189
+ Schema::Field::Integer.new(field)
190
+ when 'number', 'boolean'
191
+ Schema::Field::Number.new(field)
192
+ when 'date_time', 'timestamp'
193
+ Schema::Field::DateTime.new(field)
194
+ when 'date'
195
+ Schema::Field::Date.new(field)
196
+ when 'object'
197
+ field[:properties] = Fields.new(field[:properties])
198
+ field.delete(:control_type)
199
+ Schema::Field::Object.new(field)
200
+ when 'array'
201
+ of = field[:of] = (field[:of] || 'object').to_s
202
+ if of == 'object'
203
+ field[:properties] = Fields.new(field[:properties])
204
+ else
205
+ field.merge(
206
+ field_with_defaults(field.merge(type: of)).except(:render_input, :parse_output)
207
+ )
208
+ end
209
+ Schema::Field::Array.new(field)
210
+ else
211
+ Schema::Field::String.new(field)
212
+ end
213
+ end
214
+ end
215
+
216
+ private_constant :Fields
217
+ end
218
+ end
219
+ end
220
+
221
+ require_relative './schema/field/array'
222
+ require_relative './schema/field/date'
223
+ require_relative './schema/field/date_time'
224
+ require_relative './schema/field/integer'
225
+ require_relative './schema/field/number'
226
+ require_relative './schema/field/object'
227
+ require_relative './schema/field/string'
228
+
229
+ require_relative './schema/type/time'
230
+ require_relative './schema/type/unicode_string'
@@ -89,7 +89,32 @@ module Workato
89
89
  end
90
90
  end
91
91
 
92
+ def invoke(input = {}, payload = {}, headers = {}, params = {})
93
+ extended_schema = extended_schema(nil, input)
94
+ config_schema = Schema.new(schema: config_fields_schema)
95
+ input_schema = Schema.new(schema: extended_schema[:input])
96
+ output_schema = Schema.new(schema: extended_schema[:output])
97
+
98
+ input = apply_input_schema(input, config_schema + input_schema)
99
+ output = if webhook_notification?
100
+ webhook_notification(input, payload, input_schema, output_schema, headers, params)
101
+ else
102
+ poll(nil, input, nil, input_schema, output_schema)
103
+ end
104
+ output[:events].each do |event|
105
+ apply_output_schema(event, output_schema)
106
+ end
107
+
108
+ output
109
+ end
110
+
111
+ private
112
+
92
113
  alias trigger operation
114
+
115
+ def webhook_notification?
116
+ trigger[:webhook_notification].present?
117
+ end
93
118
  end
94
119
  end
95
120
  end
@@ -3,7 +3,7 @@
3
3
  module Workato
4
4
  module Connector
5
5
  module Sdk
6
- VERSION = '0.2.0'
6
+ VERSION = '0.3.0'
7
7
  end
8
8
  end
9
9
  end
@@ -98,7 +98,7 @@ module Workato
98
98
  alias encode_hex to_hex
99
99
 
100
100
  def decode_hex
101
- Binary.new([self].pack('H*'))
101
+ Extension::Binary.new([self].pack('H*'))
102
102
  end
103
103
 
104
104
  def encode_base64
@@ -106,7 +106,7 @@ module Workato
106
106
  end
107
107
 
108
108
  def decode_base64
109
- Binary.new(Base64.decode64(self))
109
+ Extension::Binary.new(Base64.decode64(self))
110
110
  end
111
111
 
112
112
  def encode_urlsafe_base64
@@ -118,21 +118,27 @@ module Workato
118
118
  end
119
119
 
120
120
  def decode_urlsafe_base64
121
- Binary.new(Base64.urlsafe_decode64(self))
121
+ Extension::Binary.new(Base64.urlsafe_decode64(self))
122
122
  end
123
123
 
124
124
  def encode_sha256
125
- Binary.new(::Digest::SHA256.digest(self))
125
+ Extension::Binary.new(::Digest::SHA256.digest(self))
126
126
  end
127
127
 
128
128
  def hmac_sha256(key)
129
129
  digest = ::OpenSSL::Digest.new('sha256')
130
- Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
130
+ Extension::Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
131
131
  end
132
132
 
133
133
  def hmac_sha512(key)
134
134
  digest = ::OpenSSL::Digest.new('sha512')
135
- Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
135
+ Extension::Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
136
+ end
137
+
138
+ def rsa_sha256(key)
139
+ digest = ::OpenSSL::Digest.new('sha256')
140
+ private_key = ::OpenSSL::PKey::RSA.new(key)
141
+ Workato::Extension::Binary.new(private_key.sign(digest, self))
136
142
  end
137
143
 
138
144
  def md5_hexdigest
@@ -140,17 +146,17 @@ module Workato
140
146
  end
141
147
 
142
148
  def sha1
143
- Binary.new(::Digest::SHA1.digest(self))
149
+ Extension::Binary.new(::Digest::SHA1.digest(self))
144
150
  end
145
151
 
146
152
  def hmac_sha1(key)
147
153
  digest = ::OpenSSL::Digest.new('sha1')
148
- Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
154
+ Extension::Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
149
155
  end
150
156
 
151
157
  def hmac_md5(key)
152
158
  digest = ::OpenSSL::Digest.new('md5')
153
- Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
159
+ Extension::Binary.new(::OpenSSL::HMAC.digest(digest, key, self))
154
160
  end
155
161
 
156
162
  def from_xml
@@ -228,7 +234,7 @@ module Workato
228
234
  end
229
235
 
230
236
  def as_string(encoding)
231
- String.new(self, encoding: encoding).encode(encoding, invalid: :replace, undef: :replace)
237
+ ::String.new(self, encoding: encoding).encode(encoding, invalid: :replace, undef: :replace)
232
238
  end
233
239
 
234
240
  def as_utf8
@@ -5,9 +5,15 @@ RSpec.describe 'actions/<%= name %>', :vcr do
5
5
  # Spec describes the most commons blocks of an action. Remove describes that you don't need.
6
6
  # Learn more: https://docs.workato.com/developing-connectors/sdk/cli/reference/rspec-commands.html
7
7
 
8
+ subject(:output) { connector.actions.<%= name %>(input) }
9
+
8
10
  let(:connector) { Workato::Connector::Sdk::Connector.from_file('connector.rb', settings) }
9
11
  let(:settings) { Workato::Connector::Sdk::Settings.from_default_file }
12
+ let(:input) { {} }
13
+
14
+ pending 'add some examples for action output'
10
15
 
16
+ # Or add more fine grained tests for each action definition block
11
17
  let(:action) { connector.actions.<%= name %> }
12
18
 
13
19
  describe 'execute' do
@@ -6,9 +6,15 @@ RSpec.describe 'triggers/<%= name %>', :vcr do
6
6
  # Depending on the type of your trigger remove describes that you don't need.
7
7
  # Learn more: https://docs.workato.com/developing-connectors/sdk/cli/reference/rspec-commands.html
8
8
 
9
+ subject(:output) { connector.triggers.<%= name %>(input) }
10
+
9
11
  let(:connector) { Workato::Connector::Sdk::Connector.from_file('connector.rb', settings) }
10
12
  let(:settings) { Workato::Connector::Sdk::Settings.from_default_file }
13
+ let(:input) { {} }
14
+
15
+ pending 'add some examples for trigger output'
11
16
 
17
+ # Or add more fine grained tests for each trigger definition block
12
18
  let(:trigger) { connector.triggers.<%= name %> }
13
19
 
14
20
  describe 'webhook_subscribe' do
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: 0.2.0
4
+ version: 0.3.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: 2021-10-20 00:00:00.000000000 Z
11
+ date: 2021-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -386,6 +386,17 @@ files:
386
386
  - lib/workato/connector/sdk/object_definitions.rb
387
387
  - lib/workato/connector/sdk/operation.rb
388
388
  - lib/workato/connector/sdk/request.rb
389
+ - lib/workato/connector/sdk/schema.rb
390
+ - lib/workato/connector/sdk/schema/field/array.rb
391
+ - lib/workato/connector/sdk/schema/field/convertors.rb
392
+ - lib/workato/connector/sdk/schema/field/date.rb
393
+ - lib/workato/connector/sdk/schema/field/date_time.rb
394
+ - lib/workato/connector/sdk/schema/field/integer.rb
395
+ - lib/workato/connector/sdk/schema/field/number.rb
396
+ - lib/workato/connector/sdk/schema/field/object.rb
397
+ - lib/workato/connector/sdk/schema/field/string.rb
398
+ - lib/workato/connector/sdk/schema/type/time.rb
399
+ - lib/workato/connector/sdk/schema/type/unicode_string.rb
389
400
  - lib/workato/connector/sdk/settings.rb
390
401
  - lib/workato/connector/sdk/summarize.rb
391
402
  - lib/workato/connector/sdk/trigger.rb
@@ -442,8 +453,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
442
453
  - !ruby/object:Gem::Version
443
454
  version: '0'
444
455
  requirements: []
445
- rubyforge_project:
446
- rubygems_version: 2.6.14.4
456
+ rubygems_version: 3.2.3
447
457
  signing_key:
448
458
  specification_version: 4
449
459
  summary: Gem for running adapter's code outside Workato infrastructure