workato-connector-sdk 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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