workato-connector-sdk 0.1.0 → 0.3.0

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