workato-connector-sdk 0.1.0

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