workato-connector-sdk 0.1.1 → 0.4.0

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