workato-connector-sdk 0.1.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -30
  3. data/exe/workato +12 -1
  4. data/lib/workato/cli/edit_command.rb +1 -3
  5. data/lib/workato/cli/exec_command.rb +21 -5
  6. data/lib/workato/cli/generate_command.rb +1 -2
  7. data/lib/workato/cli/generators/connector_generator.rb +4 -1
  8. data/lib/workato/cli/main.rb +50 -3
  9. data/lib/workato/cli/oauth2_command.rb +180 -0
  10. data/lib/workato/cli/push_command.rb +36 -24
  11. data/lib/workato/connector/sdk/action.rb +52 -4
  12. data/lib/workato/connector/sdk/connection.rb +144 -0
  13. data/lib/workato/connector/sdk/connector.rb +24 -9
  14. data/lib/workato/connector/sdk/dsl/time.rb +8 -1
  15. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +4 -4
  16. data/lib/workato/connector/sdk/errors.rb +13 -0
  17. data/lib/workato/connector/sdk/object_definitions.rb +10 -10
  18. data/lib/workato/connector/sdk/operation.rb +37 -39
  19. data/lib/workato/connector/sdk/request.rb +11 -7
  20. data/lib/workato/connector/sdk/schema/field/array.rb +25 -0
  21. data/lib/workato/connector/sdk/schema/field/convertors.rb +189 -0
  22. data/lib/workato/connector/sdk/schema/field/date.rb +28 -0
  23. data/lib/workato/connector/sdk/schema/field/date_time.rb +28 -0
  24. data/lib/workato/connector/sdk/schema/field/integer.rb +27 -0
  25. data/lib/workato/connector/sdk/schema/field/number.rb +27 -0
  26. data/lib/workato/connector/sdk/schema/field/object.rb +25 -0
  27. data/lib/workato/connector/sdk/schema/field/string.rb +26 -0
  28. data/lib/workato/connector/sdk/schema/type/time.rb +53 -0
  29. data/lib/workato/connector/sdk/schema/type/unicode_string.rb +22 -0
  30. data/lib/workato/connector/sdk/schema.rb +230 -0
  31. data/lib/workato/connector/sdk/settings.rb +6 -3
  32. data/lib/workato/connector/sdk/trigger.rb +25 -0
  33. data/lib/workato/connector/sdk/version.rb +1 -1
  34. data/lib/workato/connector/sdk.rb +1 -0
  35. data/lib/workato/extension/string.rb +16 -10
  36. data/lib/workato/web/app.rb +23 -0
  37. data/templates/Gemfile.erb +1 -0
  38. data/templates/spec/action_spec.rb.erb +7 -1
  39. data/templates/spec/trigger_spec.rb.erb +6 -0
  40. metadata +95 -7
@@ -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
@@ -16,21 +17,23 @@ module Workato
16
17
 
17
18
  cattr_accessor :on_settings_updated
18
19
 
19
- def initialize(operation:, connection: {}, methods: {}, settings: {}, object_definitions: nil)
20
- @settings = settings.with_indifferent_access
20
+ def initialize(connection:, operation: {}, methods: {}, settings: {}, object_definitions: nil)
21
+ @connection = connection
22
+ @settings = settings
21
23
  @operation = operation.with_indifferent_access
22
- @connection = connection.with_indifferent_access
23
24
  @_methods = methods.with_indifferent_access
24
25
  @object_definitions = object_definitions
25
26
  end
26
27
 
27
- def execute(settings = nil, input = {}, extended_input_schema = [], extended_output_schema = [], &block)
28
+ def execute(settings = nil, input = {}, extended_input_schema = [], extended_output_schema = [], continue = {},
29
+ &block)
28
30
  @settings = settings.with_indifferent_access if settings # is being used in request for refresh tokens
29
31
  request_or_result = instance_exec(
30
32
  @settings.with_indifferent_access, # a copy of settings hash is being used in executable blocks
31
33
  input.with_indifferent_access,
32
34
  Array.wrap(extended_input_schema).map(&:with_indifferent_access),
33
35
  Array.wrap(extended_output_schema).map(&:with_indifferent_access),
36
+ continue.with_indifferent_access,
34
37
  &block
35
38
  )
36
39
  resolve_request(request_or_result)
@@ -69,9 +72,9 @@ module Workato
69
72
  def refresh_authorization!(http_code, http_body, exception, settings = {})
70
73
  return unless refresh_auth?(http_code, http_body, exception)
71
74
 
72
- new_settings = if /oauth2/i =~ connection[:authorization][:type]
75
+ new_settings = if /oauth2/i =~ connection.authorization.type
73
76
  refresh_oauth2_token(settings)
74
- elsif connection[:authorization][:acquire]
77
+ elsif connection.authorization.acquire?
75
78
  acquire_token(settings)
76
79
  end
77
80
  return unless new_settings
@@ -85,6 +88,23 @@ module Workato
85
88
 
86
89
  private
87
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
+
88
108
  def summarize(data, paths)
89
109
  return data unless paths.present?
90
110
 
@@ -92,7 +112,7 @@ module Workato
92
112
  end
93
113
 
94
114
  def schema_fields(object_definitions_hash, settings, config_fields, &schema_proc)
95
- return {} unless schema_proc
115
+ return [] unless schema_proc
96
116
 
97
117
  execute(settings, config_fields) do |connection, input|
98
118
  instance_exec(
@@ -132,7 +152,7 @@ module Workato
132
152
  end
133
153
 
134
154
  def refresh_auth?(http_code, http_body, exception)
135
- refresh_on = Array.wrap(connection[:authorization][:refresh_on]).compact
155
+ refresh_on = connection.authorization.refresh_on
136
156
  refresh_on.blank? || refresh_on.any? do |pattern|
137
157
  pattern.is_a?(::Integer) && pattern == http_code ||
138
158
  pattern === exception&.to_s ||
@@ -141,48 +161,26 @@ module Workato
141
161
  end
142
162
 
143
163
  def acquire_token(settings)
144
- acquire = connection[:authorization][:acquire]
145
- raise InvalidDefinitionError, "'acquire' block is required for authorization" unless acquire
146
-
147
- Action.new(
148
- action: {
149
- execute: ->(connection) { instance_exec(connection, &acquire) }
150
- },
151
- connection: connection.merge(
152
- authorization: connection[:authorization].merge(
153
- apply: nil
154
- )
155
- ),
156
- methods: @_methods
157
- ).execute(settings)
164
+ connection.authorization.acquire(settings)
158
165
  end
159
166
 
160
167
  def refresh_oauth2_token_using_refresh(settings)
161
- refresh = connection[:authorization][:refresh]
162
- new_tokens, new_settings = Action.new(
163
- action: {
164
- execute: lambda do |connection|
165
- instance_exec(connection, connection[:refresh_token], &refresh)
166
- end
167
- },
168
- methods: @_methods
169
- ).execute(settings)
170
-
168
+ new_tokens, new_settings = connection.authorization.refresh(settings, settings[:refresh_token])
171
169
  new_tokens.with_indifferent_access.merge(new_settings || {})
172
170
  end
173
171
 
174
172
  def refresh_oauth2_token_using_token_url(settings)
175
173
  if settings[:refresh_token].blank?
176
- raise NotImplementedError, 'workato-connector-sdk does not support OAuth2 authorization process. '\
177
- 'Use Workato Debugger UI to acquire access_token and refresh_token'
174
+ raise NotImplementedError, 'refresh_token is empty. ' \
175
+ 'Use workato oauth2 command to acquire access_token and refresh_token'
178
176
  end
179
177
 
180
178
  response = RestClient::Request.execute(
181
- url: connection[:authorization][:token_url].call(settings),
179
+ url: connection.authorization.token_url(settings),
182
180
  method: :post,
183
181
  payload: {
184
- client_id: connection[:authorization][:client_id].call(settings),
185
- client_secret: connection[:authorization][:client_secret].call(settings),
182
+ client_id: connection.authorization.client_id(settings),
183
+ client_secret: connection.authorization.client_secret(settings),
186
184
  grant_type: :refresh_token,
187
185
  refresh_token: settings[:refresh_token]
188
186
  },
@@ -198,9 +196,9 @@ module Workato
198
196
  end
199
197
 
200
198
  def refresh_oauth2_token(settings)
201
- if connection[:authorization][:refresh]
199
+ if connection.authorization.refresh?
202
200
  refresh_oauth2_token_using_refresh(settings)
203
- elsif connection[:authorization][:token_url]
201
+ elsif connection.authorization.token_url?
204
202
  refresh_oauth2_token_using_token_url(settings)
205
203
  else
206
204
  raise InvalidDefinitionError, "'refresh' block or 'token_url' is required for refreshing the token"
@@ -15,13 +15,13 @@ module Workato
15
15
  class Request < SimpleDelegator
16
16
  using BlockInvocationRefinements
17
17
 
18
- def initialize(uri, method: 'GET', connection: {}, settings: {}, action: nil)
18
+ def initialize(uri, method: 'GET', settings: {}, connection: nil, action: nil)
19
19
  super(nil)
20
20
  @uri = uri
21
- @authorization = (connection[:authorization] || {}).with_indifferent_access
22
- @settings = settings
23
- @base_uri = connection[:base_uri]&.call(settings.with_indifferent_access)
24
21
  @method = method
22
+ @settings = settings
23
+ @authorization = connection&.authorization
24
+ @base_uri = connection&.base_uri(settings)
25
25
  @action = action
26
26
  @headers = {}
27
27
  @case_sensitive_headers = {}
@@ -321,7 +321,9 @@ module Workato
321
321
  end
322
322
 
323
323
  def detect_error!(response)
324
- error_patterns = Array.wrap(@authorization[:detect_on])
324
+ return unless @authorization
325
+
326
+ error_patterns = @authorization.detect_on
325
327
  return unless error_patterns.any? { |pattern| pattern === response rescue false }
326
328
 
327
329
  Kernel.raise(CustomRequestError, response.to_s)
@@ -357,13 +359,15 @@ module Workato
357
359
  end
358
360
 
359
361
  def authorized
360
- apply = @authorization[:apply] || @authorization[:credentials]
362
+ return yield unless @authorization
363
+
364
+ apply = @authorization.source[:apply] || @authorization.source[:credentials]
361
365
  return yield unless apply
362
366
 
363
367
  first = true
364
368
  begin
365
369
  settings = @settings.with_indifferent_access
366
- if /oauth2/i =~ @authorization[:type]
370
+ if /oauth2/i =~ @authorization.type
367
371
  instance_exec(settings, settings[:access_token], @auth_type, &apply)
368
372
  else
369
373
  instance_exec(settings, @auth_type, &apply)
@@ -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