workato-connector-sdk 0.1.1 → 0.4.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 (44) hide show
  1. checksums.yaml +5 -5
  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 +23 -7
  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/aws.rb +225 -0
  15. data/lib/workato/connector/sdk/dsl/error.rb +1 -1
  16. data/lib/workato/connector/sdk/dsl/http.rb +19 -0
  17. data/lib/workato/connector/sdk/dsl/time.rb +8 -1
  18. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +21 -4
  19. data/lib/workato/connector/sdk/dsl.rb +2 -0
  20. data/lib/workato/connector/sdk/errors.rb +16 -1
  21. data/lib/workato/connector/sdk/object_definitions.rb +10 -10
  22. data/lib/workato/connector/sdk/operation.rb +38 -40
  23. data/lib/workato/connector/sdk/request.rb +11 -7
  24. data/lib/workato/connector/sdk/schema/field/array.rb +25 -0
  25. data/lib/workato/connector/sdk/schema/field/convertors.rb +189 -0
  26. data/lib/workato/connector/sdk/schema/field/date.rb +28 -0
  27. data/lib/workato/connector/sdk/schema/field/date_time.rb +28 -0
  28. data/lib/workato/connector/sdk/schema/field/integer.rb +27 -0
  29. data/lib/workato/connector/sdk/schema/field/number.rb +27 -0
  30. data/lib/workato/connector/sdk/schema/field/object.rb +25 -0
  31. data/lib/workato/connector/sdk/schema/field/string.rb +26 -0
  32. data/lib/workato/connector/sdk/schema/type/time.rb +53 -0
  33. data/lib/workato/connector/sdk/schema/type/unicode_string.rb +22 -0
  34. data/lib/workato/connector/sdk/schema.rb +230 -0
  35. data/lib/workato/connector/sdk/settings.rb +6 -3
  36. data/lib/workato/connector/sdk/trigger.rb +25 -0
  37. data/lib/workato/connector/sdk/version.rb +1 -1
  38. data/lib/workato/connector/sdk.rb +1 -0
  39. data/lib/workato/extension/string.rb +20 -10
  40. data/lib/workato/web/app.rb +23 -0
  41. data/templates/Gemfile.erb +1 -0
  42. data/templates/spec/action_spec.rb.erb +7 -1
  43. data/templates/spec/trigger_spec.rb.erb +6 -0
  44. metadata +102 -4
@@ -6,6 +6,10 @@ module Workato
6
6
  module Dsl
7
7
  # https://docs.workato.com/developing-connectors/sdk/sdk-reference/http.html#http-methods
8
8
  module HTTP
9
+ PARALLEL_SUCCESS_INDEX = 0
10
+ PARALLEL_RESULTS_INDEX = 1
11
+ PARALLEL_ERRORS_INDEX = 2
12
+
9
13
  def get(url, params = {})
10
14
  http_request(url, method: 'GET').params(params).response_format_json
11
15
  end
@@ -42,6 +46,21 @@ module Workato
42
46
  http_request(url, method: 'MOVE').payload(payload).format_json
43
47
  end
44
48
 
49
+ def parallel(requests = [], threads: 1, rpm: nil, requests_per_period: nil, period: 1.minute.to_i) # rubocop:disable Lint/UnusedMethodArgument
50
+ requests.each.with_object([true, [], []]) do |request, result|
51
+ response = nil
52
+ exception = nil
53
+ begin
54
+ response = request.execute!
55
+ rescue RequestError, RuntimeError => e
56
+ exception = e.to_s
57
+ end
58
+ result[PARALLEL_SUCCESS_INDEX] &&= exception.nil?
59
+ result[PARALLEL_RESULTS_INDEX] << response
60
+ result[PARALLEL_ERRORS_INDEX] << exception
61
+ end
62
+ end
63
+
45
64
  private
46
65
 
47
66
  def http_request(url, method:)
@@ -18,4 +18,11 @@ module Workato
18
18
  end
19
19
  end
20
20
 
21
- ::Time.zone = Workato::Connector::Sdk::DEFAULT_TIME_ZONE
21
+ begin
22
+ ::Time.zone = Workato::Connector::Sdk::DEFAULT_TIME_ZONE
23
+ rescue TZInfo::DataSourceNotFound
24
+ puts ''
25
+ puts "tzinfo-data is not present. Please install gem 'tzinfo-data' by 'gem install tzinfo-data'"
26
+ puts ''
27
+ exit!
28
+ end
@@ -10,6 +10,8 @@ module Workato
10
10
  JWT_ALGORITHMS = %w[RS256 RS384 RS512].freeze
11
11
  JWT_RSA_KEY_MIN_LENGTH = 2048
12
12
 
13
+ VERIFY_RCA_ALGORITHMS = %w[SHA SHA1 SHA224 SHA256 SHA384 SHA512].freeze
14
+
13
15
  def workato
14
16
  WorkatoCodeLib
15
17
  end
@@ -38,6 +40,21 @@ module Workato
38
40
  ::JWT.encode(payload, rsa_private, algorithm, header_fields)
39
41
  end
40
42
 
43
+ def verify_rsa(payload, certificate, signature, algorithm = 'SHA256')
44
+ algorithm = algorithm.to_s.upcase
45
+ unless VERIFY_RCA_ALGORITHMS.include?(algorithm)
46
+ raise "Unsupported signing method. Supports only #{VERIFY_RCA_ALGORITHMS.join(', ')}. Got: '#{algorithm}'" # rubocop:disable Layout/LineLength
47
+ end
48
+
49
+ cert = OpenSSL::X509::Certificate.new(certificate)
50
+ digest = OpenSSL::Digest.new(algorithm)
51
+ cert.public_key.verify(digest, signature, payload)
52
+ rescue OpenSSL::PKey::PKeyError
53
+ raise 'An error occurred during signature verification. Check arguments'
54
+ rescue OpenSSL::X509::CertificateError
55
+ raise 'Invalid certificate format'
56
+ end
57
+
41
58
  def parse_yaml(yaml)
42
59
  ::Psych.safe_load(yaml)
43
60
  rescue ::Psych::DisallowedClass => e
@@ -63,7 +80,7 @@ module Workato
63
80
  raise "The requested length or random bytes sequence should be <= #{RANDOM_SIZE}"
64
81
  end
65
82
 
66
- String::Binary.new(::OpenSSL::Random.random_bytes(len))
83
+ Extension::Binary.new(::OpenSSL::Random.random_bytes(len))
67
84
  end
68
85
 
69
86
  ALLOWED_KEY_SIZES = [128, 192, 256].freeze
@@ -78,7 +95,7 @@ module Workato
78
95
  cipher.encrypt
79
96
  cipher.key = key
80
97
  cipher.iv = init_vector if init_vector.present?
81
- String::Binary.new(cipher.update(string) + cipher.final)
98
+ Extension::Binary.new(cipher.update(string) + cipher.final)
82
99
  end
83
100
 
84
101
  def aes_cbc_decrypt(string, key, init_vector = nil)
@@ -91,11 +108,11 @@ module Workato
91
108
  cipher.decrypt
92
109
  cipher.key = key
93
110
  cipher.iv = init_vector if init_vector.present?
94
- String::Binary.new(cipher.update(string) + cipher.final)
111
+ Extension::Binary.new(cipher.update(string) + cipher.final)
95
112
  end
96
113
 
97
114
  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))
115
+ Extension::Binary.new(::OpenSSL::PKCS5.pbkdf2_hmac_sha1(string, salt, iterations, key_len))
99
116
  end
100
117
  end
101
118
  end
@@ -10,6 +10,7 @@ require_relative './dsl/lookup_table'
10
10
  require_relative './dsl/workato_code_lib'
11
11
  require_relative './dsl/workato_schema'
12
12
  require_relative './dsl/time'
13
+ require_relative './dsl/aws'
13
14
 
14
15
  module Workato
15
16
  module Connector
@@ -21,6 +22,7 @@ module Workato
21
22
  include LookupTable
22
23
  include WorkatoCodeLib
23
24
  include WorkatoSchema
25
+ include AWS
24
26
 
25
27
  def sleep(seconds)
26
28
  ::Kernel.sleep(seconds.presence || 0)
@@ -5,8 +5,12 @@ 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
 
12
+ RuntimeError = Class.new(StandardError)
13
+
10
14
  class RequestError < StandardError
11
15
  attr_reader :method,
12
16
  :code,
@@ -20,11 +24,22 @@ module Workato
20
24
  end
21
25
  end
22
26
 
23
- class NotImplementedError < RuntimeError
27
+ class NotImplementedError < StandardError
24
28
  def initialize(msg = 'This part of Connector SDK is not implemented in workato-connector-sdk yet')
25
29
  super
26
30
  end
27
31
  end
32
+
33
+ class MissingRequiredInput < StandardError
34
+ def initialize(label, toggle_label)
35
+ message = if toggle_label && label != toggle_label
36
+ "Either '#{label}' or '#{toggle_label}' must be present"
37
+ else
38
+ "'#{label}' must be present"
39
+ end
40
+ super(message)
41
+ end
42
+ end
28
43
  end
29
44
  end
30
45
  end
@@ -10,23 +10,23 @@ module Workato
10
10
 
11
11
  def initialize(object_definitions:, connection:, methods:, settings:)
12
12
  @object_definitions_source = object_definitions
13
- @methods = methods
13
+ @methods_source = methods
14
14
  @connection = connection
15
15
  @settings = settings
16
16
  define_object_definition_methods(object_definitions)
17
17
  end
18
18
 
19
19
  def lazy(settings = nil, config_fields = {})
20
- object_definitions_lazy_hash = DupHashWithIndifferentAccess.new do |h, name|
21
- fields_proc = @object_definitions_source[name][:fields]
22
- h[name] = Action.new(
20
+ DupHashWithIndifferentAccess.new do |object_definitions, name|
21
+ fields_proc = object_definitions_source[name][:fields]
22
+ object_definitions[name] = Action.new(
23
23
  action: {
24
24
  execute: lambda do |connection, input|
25
- instance_exec(connection, input, object_definitions_lazy_hash, &fields_proc)
25
+ instance_exec(connection, input, object_definitions, &fields_proc)
26
26
  end
27
27
  },
28
- methods: @methods,
29
- connection: @connection,
28
+ methods: methods_source,
29
+ connection: connection,
30
30
  settings: @settings
31
31
  ).execute(settings, config_fields)
32
32
  end
@@ -34,10 +34,10 @@ module Workato
34
34
 
35
35
  private
36
36
 
37
- attr_reader :methods,
37
+ attr_reader :methods_source,
38
38
  :connection,
39
- :objects,
40
- :settings
39
+ :settings,
40
+ :object_definitions_source
41
41
 
42
42
  def define_object_definition_methods(object_definitions)
43
43
  object_definitions.each do |(object, _definition)|
@@ -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,22 +72,39 @@ 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
78
81
 
79
82
  settings.merge!(new_settings)
80
83
 
81
- on_settings_updated&.call(http_body, http_code, exception, settings)
84
+ on_settings_updated&.call("Refresh token triggered on response \"#{exception}\"", settings)
82
85
 
83
86
  settings
84
87
  end
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