workato-connector-sdk 0.1.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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +20 -0
  3. data/README.md +1093 -0
  4. data/exe/workato +5 -0
  5. data/lib/workato/cli/edit_command.rb +71 -0
  6. data/lib/workato/cli/exec_command.rb +191 -0
  7. data/lib/workato/cli/generate_command.rb +105 -0
  8. data/lib/workato/cli/generators/connector_generator.rb +94 -0
  9. data/lib/workato/cli/generators/master_key_generator.rb +56 -0
  10. data/lib/workato/cli/main.rb +142 -0
  11. data/lib/workato/cli/push_command.rb +208 -0
  12. data/lib/workato/connector/sdk/account_properties.rb +60 -0
  13. data/lib/workato/connector/sdk/action.rb +88 -0
  14. data/lib/workato/connector/sdk/block_invocation_refinements.rb +30 -0
  15. data/lib/workato/connector/sdk/connector.rb +230 -0
  16. data/lib/workato/connector/sdk/dsl/account_property.rb +15 -0
  17. data/lib/workato/connector/sdk/dsl/call.rb +17 -0
  18. data/lib/workato/connector/sdk/dsl/error.rb +15 -0
  19. data/lib/workato/connector/sdk/dsl/http.rb +60 -0
  20. data/lib/workato/connector/sdk/dsl/lookup_table.rb +15 -0
  21. data/lib/workato/connector/sdk/dsl/time.rb +21 -0
  22. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +105 -0
  23. data/lib/workato/connector/sdk/dsl/workato_schema.rb +15 -0
  24. data/lib/workato/connector/sdk/dsl.rb +46 -0
  25. data/lib/workato/connector/sdk/errors.rb +30 -0
  26. data/lib/workato/connector/sdk/lookup_tables.rb +62 -0
  27. data/lib/workato/connector/sdk/object_definitions.rb +74 -0
  28. data/lib/workato/connector/sdk/operation.rb +217 -0
  29. data/lib/workato/connector/sdk/request.rb +399 -0
  30. data/lib/workato/connector/sdk/settings.rb +130 -0
  31. data/lib/workato/connector/sdk/summarize.rb +61 -0
  32. data/lib/workato/connector/sdk/trigger.rb +96 -0
  33. data/lib/workato/connector/sdk/version.rb +9 -0
  34. data/lib/workato/connector/sdk/workato_schemas.rb +37 -0
  35. data/lib/workato/connector/sdk/xml.rb +35 -0
  36. data/lib/workato/connector/sdk.rb +58 -0
  37. data/lib/workato/extension/array.rb +124 -0
  38. data/lib/workato/extension/case_sensitive_headers.rb +51 -0
  39. data/lib/workato/extension/currency.rb +15 -0
  40. data/lib/workato/extension/date.rb +14 -0
  41. data/lib/workato/extension/enumerable.rb +55 -0
  42. data/lib/workato/extension/extra_chain_cert.rb +40 -0
  43. data/lib/workato/extension/hash.rb +13 -0
  44. data/lib/workato/extension/integer.rb +17 -0
  45. data/lib/workato/extension/nil_class.rb +17 -0
  46. data/lib/workato/extension/object.rb +38 -0
  47. data/lib/workato/extension/phone.rb +14 -0
  48. data/lib/workato/extension/string.rb +268 -0
  49. data/lib/workato/extension/symbol.rb +13 -0
  50. data/lib/workato/extension/time.rb +13 -0
  51. data/lib/workato/testing/vcr_encrypted_cassette_serializer.rb +38 -0
  52. data/lib/workato/testing/vcr_multipart_body_matcher.rb +32 -0
  53. data/lib/workato-connector-sdk.rb +3 -0
  54. data/templates/.rspec.erb +3 -0
  55. data/templates/Gemfile.erb +10 -0
  56. data/templates/connector.rb.erb +37 -0
  57. data/templates/spec/action_spec.rb.erb +36 -0
  58. data/templates/spec/connector_spec.rb.erb +18 -0
  59. data/templates/spec/method_spec.rb.erb +13 -0
  60. data/templates/spec/object_definition_spec.rb.erb +18 -0
  61. data/templates/spec/pick_list_spec.rb.erb +13 -0
  62. data/templates/spec/spec_helper.rb.erb +38 -0
  63. data/templates/spec/trigger_spec.rb.erb +61 -0
  64. metadata +372 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ class Summarize
7
+ ARRAY_SUMMARIZATION_LIMIT = 100
8
+ STRING_SUMMARIZATION_LIMIT = 1024
9
+
10
+ def initialize(data:, paths:)
11
+ @paths = paths
12
+ @data = data
13
+ end
14
+
15
+ def call
16
+ return data if paths.blank?
17
+
18
+ summarized = data
19
+ paths.each do |path|
20
+ steps = path.split('.')
21
+ if above_summarization_limit?(summarized.dig(*steps))
22
+ summarized = data.deep_dup if summarized.equal?(data)
23
+ apply_summarization_limit(summarized, steps)
24
+ end
25
+ end
26
+
27
+ summarized
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :data,
33
+ :paths
34
+
35
+ def above_summarization_limit?(candidate)
36
+ candidate.is_a?(::Array) && candidate.length > ARRAY_SUMMARIZATION_LIMIT ||
37
+ candidate.is_a?(::String) && candidate.length > STRING_SUMMARIZATION_LIMIT
38
+ end
39
+
40
+ def apply_summarization_limit(summarized, steps)
41
+ container = if steps.length > 1
42
+ summarized.dig(*steps[0..-2])
43
+ else
44
+ summarized
45
+ end
46
+ candidate = container[steps.last]
47
+ case candidate
48
+ when Array
49
+ candidate[(ARRAY_SUMMARIZATION_LIMIT - 2)..-2] =
50
+ "... #{candidate.length - ARRAY_SUMMARIZATION_LIMIT} items ..."
51
+ when String
52
+ candidate[(STRING_SUMMARIZATION_LIMIT - 1)..-1] =
53
+ "... #{candidate.length - STRING_SUMMARIZATION_LIMIT} characters ..."
54
+ else
55
+ candidate
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Trigger < Operation
9
+ using BlockInvocationRefinements
10
+
11
+ def initialize(trigger:, connection: {}, methods: {}, settings: {}, object_definitions: nil)
12
+ super(
13
+ operation: trigger,
14
+ connection: connection,
15
+ methods: methods,
16
+ settings: settings,
17
+ object_definitions: object_definitions
18
+ )
19
+ end
20
+
21
+ def poll_page(settings = nil, input = {}, closure = nil, extended_input_schema = [],
22
+ extended_output_schema = [])
23
+ poll_proc = trigger[:poll]
24
+ output = execute(
25
+ settings,
26
+ { input: input, closure: closure },
27
+ extended_input_schema,
28
+ extended_output_schema
29
+ ) do |connection, payload, eis, eos|
30
+ instance_exec(connection, payload[:input], payload[:closure], eis, eos, &poll_proc)
31
+ end
32
+ output[:events] = ::Array.wrap(output[:events]).reverse!.uniq(&trigger[:dedup])
33
+ output[:next_poll] = output[:next_poll].presence || closure
34
+ output
35
+ end
36
+
37
+ def poll(settings = nil, input = {}, closure = nil, extended_input_schema = [], extended_output_schema = [])
38
+ events = []
39
+
40
+ loop do
41
+ output = poll_page(settings, input, closure, extended_input_schema, extended_output_schema)
42
+ events = output[:events] + events
43
+ closure = output[:next_poll]
44
+
45
+ break unless output[:can_poll_more]
46
+ end
47
+
48
+ {
49
+ events: events.uniq(&trigger[:dedup]),
50
+ can_poll_more: false,
51
+ next_poll: closure
52
+ }.with_indifferent_access
53
+ end
54
+
55
+ def dedup(input = {})
56
+ trigger[:dedup].call(input)
57
+ end
58
+
59
+ def webhook_notification(input = {}, payload = {}, extended_input_schema = [],
60
+ extended_output_schema = [], headers = {}, params = {})
61
+ Dsl::WithDsl.execute(
62
+ input.with_indifferent_access,
63
+ payload.with_indifferent_access,
64
+ extended_input_schema.map(&:with_indifferent_access),
65
+ extended_output_schema.map(&:with_indifferent_access),
66
+ headers.with_indifferent_access,
67
+ params.with_indifferent_access,
68
+ &trigger[:webhook_notification]
69
+ )
70
+ end
71
+
72
+ def webhook_subscribe(webhook_url = '', settings = nil, input = {}, recipe_id = SecureRandom.uuid)
73
+ webhook_subscribe_proc = trigger[:webhook_subscribe]
74
+ execute(settings, { input: input, webhook_url: webhook_url, recipe_id: recipe_id }) do |connection, payload|
75
+ instance_exec(
76
+ payload[:webhook_url],
77
+ connection,
78
+ payload[:input],
79
+ payload[:recipe_id],
80
+ &webhook_subscribe_proc
81
+ )
82
+ end
83
+ end
84
+
85
+ def webhook_unsubscribe(webhook_subscribe_output = {})
86
+ webhook_unsubscribe_proc = trigger[:webhook_unsubscribe]
87
+ execute(nil, webhook_subscribe_output) do |_connection, input|
88
+ instance_exec(input, &webhook_unsubscribe_proc)
89
+ end
90
+ end
91
+
92
+ alias trigger operation
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class WorkatoSchemas
9
+ include Singleton
10
+
11
+ class << self
12
+ def from_json(path = DEFAULT_SCHEMAS_PATH)
13
+ load_data(JSON.parse(File.read(path)))
14
+ end
15
+
16
+ delegate :find,
17
+ :load_data,
18
+ to: :instance
19
+ end
20
+
21
+ def load_data(data)
22
+ @schemas_by_id ||= {}.with_indifferent_access
23
+ @schemas_by_id.merge!(data.stringify_keys)
24
+ end
25
+
26
+ def find(id)
27
+ unless @schemas_by_id
28
+ raise 'Workato Schemas are not initialized. ' \
29
+ 'Init data by calling WorkatoSchemas.from_json or WorkatoSchemas.load_data'
30
+ end
31
+
32
+ @schemas_by_id.fetch(id.to_s)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ module Xml
9
+ def self.parse_xml_to_hash(payload, strip_namespaces: false)
10
+ parse_options = Nokogiri::XML::ParseOptions.new.nonet
11
+ lazy_reader = Nokogiri::XML::Reader(payload, nil, nil, parse_options).to_enum.lazy
12
+ lazy_reader.each_with_object([{}]) do |node, ancestors|
13
+ ancestors.shift while ancestors.count > node.depth + 1
14
+ case node.node_type
15
+ when Nokogiri::XML::Reader::TYPE_ELEMENT
16
+ element = HashWithIndifferentAccess.new
17
+ node.attributes&.each do |name, value|
18
+ element["@#{strip_namespaces ? name[/(?:^xmlns:)?[^:]+$/] : name}"] = value
19
+ end
20
+ (ancestors.first[strip_namespaces ? node.name[/[^:]+$/] : node.name] ||= []).push(element)
21
+ ancestors.unshift(element)
22
+ when Nokogiri::XML::Reader::TYPE_TEXT, Nokogiri::XML::Reader::TYPE_CDATA
23
+ element = ancestors.first
24
+ if element.key?(:content!)
25
+ element[:content!] += node.value
26
+ else
27
+ element[:content!] = node.value
28
+ end
29
+ end
30
+ end.last
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ DEFAULT_MASTER_KEY_ENV = 'WORKATO_CONNECTOR_MASTER_KEY'
7
+ DEFAULT_MASTER_KEY_PATH = 'master.key'
8
+
9
+ DEFAULT_CONNECTOR_PATH = 'connector.rb'
10
+
11
+ DEFAULT_SETTINGS_PATH = 'settings.yaml'
12
+ DEFAULT_ENCRYPTED_SETTINGS_PATH = 'settings.yaml.enc'
13
+
14
+ DEFAULT_ACCOUNT_PROPERTIES_PATH = 'account_properties.yaml'
15
+ DEFAULT_ENCRYPTED_ACCOUNT_PROPERTIES_PATH = 'account_properties.yaml.enc'
16
+
17
+ DEFAULT_LOOKUP_TABLES_PATH = 'lookup_tables.yaml'
18
+
19
+ DEFAULT_TIME_ZONE = 'Pacific Time (US & Canada)'
20
+
21
+ DEFAULT_SCHEMAS_PATH = 'workato_schemas.json'
22
+ end
23
+ end
24
+ end
25
+
26
+ # Global libs and monkey patches
27
+ require 'active_support/all'
28
+ require 'active_support/json'
29
+ require_relative '../extension/array'
30
+ require_relative '../extension/case_sensitive_headers'
31
+ require_relative '../extension/currency'
32
+ require_relative '../extension/date'
33
+ require_relative '../extension/enumerable'
34
+ require_relative '../extension/extra_chain_cert'
35
+ require_relative '../extension/hash'
36
+ require_relative '../extension/integer'
37
+ require_relative '../extension/nil_class'
38
+ require_relative '../extension/object'
39
+ require_relative '../extension/phone'
40
+ require_relative '../extension/string'
41
+ require_relative '../extension/symbol'
42
+ require_relative '../extension/time'
43
+
44
+ require_relative './sdk/account_properties'
45
+ require_relative './sdk/action'
46
+ require_relative './sdk/connector'
47
+ require_relative './sdk/dsl'
48
+ require_relative './sdk/errors'
49
+ require_relative './sdk/lookup_tables'
50
+ require_relative './sdk/object_definitions'
51
+ require_relative './sdk/operation'
52
+ require_relative './sdk/request'
53
+ require_relative './sdk/settings'
54
+ require_relative './sdk/summarize'
55
+ require_relative './sdk/trigger'
56
+ require_relative './sdk/version'
57
+ require_relative './sdk/workato_schemas'
58
+ require_relative './sdk/xml'
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Extension
5
+ module Array
6
+ def to_csv(options = {})
7
+ options ||= {}
8
+ multi_line = options[:multi_line]
9
+ multi_line = true if multi_line.nil?
10
+ options = options.dup
11
+
12
+ if multi_line && first.is_a?(::Array)
13
+ options[:multi_line] = false
14
+ map { |r| r.to_csv(options) }.join
15
+ else
16
+ options.delete(:multi_line)
17
+ super(options)
18
+ end
19
+ end
20
+
21
+ def ignored(*names)
22
+ reject { |field| names.include?(field[:name]) }
23
+ end
24
+
25
+ def only(*names)
26
+ select { |field| names.include?(field[:name]) }
27
+ end
28
+
29
+ def required(*names)
30
+ map { |field| names.include?(field[:name]) ? field.merge(optional: false) : field }
31
+ end
32
+
33
+ class ArrayWhere < SimpleDelegator
34
+ def initialize(list, options = {})
35
+ @_list = list
36
+ @_operations = []
37
+ @_resolved = false
38
+ where(options || {})
39
+ end
40
+
41
+ def where(options = {})
42
+ @_operations << [options] if options.present?
43
+ self
44
+ end
45
+
46
+ def not(options = {})
47
+ if options.present?
48
+ where(options)
49
+ @_operations.last << true
50
+ end
51
+ self
52
+ end
53
+
54
+ def self.parse_operation(key)
55
+ operation = key.to_s.match(/ *[><=!]={0,1} *\Z/)
56
+ stripped_key, operation = if operation.present?
57
+ [operation.pre_match, operation.to_s.strip]
58
+ else
59
+ [key.to_s, nil]
60
+ end
61
+ keys = stripped_key.split_strip_compact('.')
62
+ keys = keys.map(&:to_sym) if key.is_a?(::Symbol)
63
+ [keys, operation]
64
+ end
65
+
66
+ def self.coerce_operands(lhs_name, lhs, rhs, operation)
67
+ if operation == 'include?'
68
+ lhs, rhs = rhs, lhs
69
+ end
70
+
71
+ if operation.match?(/[><]/) || %w[match? include?].include?(operation)
72
+ raise "The '#{lhs_name}' is nil" if lhs.nil?
73
+ raise "Can't compare '#{lhs_name}' with nil" if rhs.nil?
74
+ end
75
+
76
+ if lhs.is_a?(::Numeric) && rhs.try(:is_number?)
77
+ rhs = rhs.is_int? ? rhs.to_i : rhs.to_f
78
+ elsif rhs.is_a?(::Numeric) && lhs.try(:is_number?)
79
+ lhs = lhs.is_int? ? lhs.to_i : lhs.to_f
80
+ end
81
+ [lhs, rhs]
82
+ end
83
+
84
+ def self.nested_attr(item, keys)
85
+ keys.reduce(item) do |obj, key|
86
+ obj[key] unless obj.nil?
87
+ end
88
+ end
89
+
90
+ def self.match?(operations, item)
91
+ operations.all? do |args, negate|
92
+ result = args.all? do |key, value|
93
+ keys, operation = parse_operation(key)
94
+ compared = nested_attr(item, keys)
95
+ if operation.blank?
96
+ operation = case value
97
+ when ::Array, Range
98
+ 'include?'
99
+ when Regexp
100
+ 'match?'
101
+ else
102
+ '=='
103
+ end
104
+ end
105
+ lhs, rhs = coerce_operands(keys.join('.'), compared, value, operation)
106
+ lhs.send(operation, rhs)
107
+ end
108
+ negate ? !result : result
109
+ end
110
+ end
111
+
112
+ def __getobj__
113
+ return super if @_resolved
114
+
115
+ filtered = @_list.select { |item| self.class.match?(@_operations, item) }
116
+ @_resolved = true
117
+ __setobj__(filtered)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ Array.prepend(Workato::Extension::Array)
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rest-client'
4
+ require 'net/http'
5
+
6
+ module Workato
7
+ module Extension
8
+ module CaseSensitiveHeaders
9
+ module Net
10
+ module HTTPHeader
11
+ attr_accessor :case_sensitive_headers
12
+
13
+ def capitalize(modified_name)
14
+ return super if case_sensitive_headers.blank?
15
+
16
+ original_name = case_sensitive_headers.keys.find { |name| name.downcase == modified_name }
17
+ original_name.presence || super
18
+ end
19
+ end
20
+ end
21
+
22
+ ::Net::HTTPHeader.prepend Net::HTTPHeader
23
+ ::Net::HTTPGenericRequest.prepend Net::HTTPHeader
24
+
25
+ module RestClient
26
+ module Request
27
+ attr_accessor :case_sensitive_headers
28
+
29
+ def processed_headers
30
+ return @processed_headers if case_sensitive_headers.blank?
31
+ return case_sensitive_headers if @processed_headers.blank?
32
+
33
+ @processed_headers.merge(case_sensitive_headers)
34
+ end
35
+
36
+ def execute(&block)
37
+ # With 2.0.0+, net/http accepts URI objects in requests and handles wrapping
38
+ # IPv6 addresses in [] for use in the Host request header.
39
+ net_http_request = net_http_request_class(method).new(uri, processed_headers)
40
+ net_http_request.case_sensitive_headers = case_sensitive_headers
41
+ transmit(uri, net_http_request, payload, &block)
42
+ ensure
43
+ payload&.close
44
+ end
45
+ end
46
+ end
47
+
48
+ ::RestClient::Request.prepend RestClient::Request
49
+ end
50
+ end
51
+ end