connectors_sdk 8.2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,230 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'connectors_sdk/office365/custom_client'
10
+ require 'connectors_sdk/base/extractor'
11
+
12
+ module ConnectorsSdk
13
+ module Office365
14
+ class Extractor < ConnectorsSdk::Base::Extractor
15
+ DRIVE_IDS_CURSOR_KEY = 'drive_ids'.freeze
16
+
17
+ def yield_document_changes(modified_since: nil, break_after_page: false, &block)
18
+ drives_to_index.each do |drive|
19
+ drive_id = drive.id
20
+
21
+ if break_after_page
22
+ current_drive_id = config.cursors['current_drive_id']
23
+ if current_drive_id.present? && current_drive_id > drive_id # they come alpha sorted
24
+ next
25
+ end
26
+ config.cursors['current_drive_id'] = drive_id
27
+ end
28
+
29
+ drive_owner_name = drive.dig(:owner, :user, :displayName)
30
+ drive_name = drive.name
31
+
32
+ drive_id_to_delta_link = config.cursors.fetch(DRIVE_IDS_CURSOR_KEY, {})
33
+ begin
34
+ if start_delta_link = drive_id_to_delta_link[drive_id]
35
+ log_debug("Starting an incremental crawl with cursor for #{service_type.classify} with drive_id: #{drive_id}")
36
+ begin
37
+ yield_changes(drive_id, :start_delta_link => start_delta_link, :drive_owner_name => drive_owner_name, :drive_name => drive_name, :break_after_page => break_after_page, &block)
38
+ rescue ConnectorsSdk::Office365::CustomClient::Office365InvalidCursorsError
39
+ log_warn("Error listing changes with start_delta_link: #{start_delta_link}, falling back to full crawl")
40
+ yield_drive_items(drive_id, :drive_owner_name => drive_owner_name, :drive_name => drive_name, :break_after_page => break_after_page, &block)
41
+ end
42
+ elsif modified_since.present?
43
+ log_debug("Starting an incremental crawl using last_modified (no cursor found) for #{service_type.classify} with drive_id: #{drive_id}")
44
+ yield_changes(drive_id, :last_modified => modified_since, :drive_owner_name => drive_owner_name, :drive_name => drive_name, :break_after_page => break_after_page, &block)
45
+ else
46
+ log_debug("Starting a full crawl #{service_type.classify} with drive_id: #{drive_id}")
47
+ yield_drive_items(drive_id, :drive_owner_name => drive_owner_name, :drive_name => drive_name, :break_after_page => break_after_page, &block)
48
+ end
49
+ rescue ConnectorsSdk::Office365::CustomClient::ClientError => e
50
+ log_warn("Error searching and listing drive #{drive_id}")
51
+ capture_exception(e)
52
+ end
53
+
54
+ if break_after_page && (config.cursors['page_cursor'].present? || config.cursors['item_children_next_link'].present?)
55
+ break
56
+ end
57
+ end
58
+
59
+ if break_after_page && config.cursors['page_cursor'].blank? && config.cursors['item_children_next_link'].blank?
60
+ @completed = true
61
+ config.overwrite_cursors!(retrieve_latest_cursors)
62
+ log_debug("Completed #{modified_since.nil? ? 'full' : 'incremental'} extraction")
63
+ end
64
+
65
+ nil
66
+ end
67
+
68
+ def yield_deleted_ids(ids)
69
+ ids.each do |id|
70
+ yield id unless existing_drive_item_ids.include?(id)
71
+ end
72
+ end
73
+
74
+ def retrieve_latest_cursors
75
+ delta_links_for_drive_ids = drives_to_index.map(&:id).each_with_object({}) do |drive_id, h|
76
+ h[drive_id] = client.get_latest_delta_link(drive_id)
77
+ rescue ConnectorsSdk::Office365::CustomClient::ClientError => e
78
+ log_warn("Error getting delta link for #{drive_id}")
79
+ capture_exception(e)
80
+ raise e
81
+ end
82
+
83
+ {
84
+ DRIVE_IDS_CURSOR_KEY => delta_links_for_drive_ids
85
+ }
86
+ end
87
+
88
+ def yield_permissions(source_user_id)
89
+ permissions = [source_user_id]
90
+ client.user_groups(source_user_id, %w(id displayName)).each do |next_group|
91
+ # Adding "Members" suffix since that is how the item permissions endpoint return group permissions
92
+ permissions << "#{next_group.displayName} Members"
93
+ end
94
+
95
+ yield permissions.uniq
96
+ rescue ConnectorsSdk::Office365::CustomClient::ClientError => e
97
+ # if a user is deleted, client.user_groups will throw 404 Not Found error, saving another call to get user profile
98
+ if e.status_code == 404
99
+ log_warn("Could not find a user with id #{source_user_id}")
100
+ yield []
101
+ else
102
+ raise
103
+ end
104
+ end
105
+
106
+ def download(item)
107
+ download_url = item[:download_url]
108
+ client.download_item(download_url)
109
+ end
110
+
111
+ private
112
+
113
+ def drives
114
+ raise NotImplementedError
115
+ end
116
+
117
+ def drives_to_index
118
+ @drives_to_index ||= begin
119
+ value = if config.index_all_drives?
120
+ drives
121
+ else
122
+ drives.select { |d| config.drive_ids.include?(d.id) }
123
+ end
124
+
125
+ log_debug("Found drives to index with ids: #{value.map(&:id).join(', ')}")
126
+
127
+ value
128
+ end
129
+ end
130
+
131
+ def existing_drive_item_ids
132
+ @existing_drive_item_ids ||= Set.new.tap do |ids|
133
+ drives_to_index.each do |drive|
134
+ client.list_items(drive.id) do |item|
135
+ ids << convert_id_to_fp_id(item.id)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def adapter
142
+ raise NotImplementedError
143
+ end
144
+
145
+ def convert_id_to_fp_id(_id)
146
+ raise NotImplementedError
147
+ end
148
+
149
+ def capture_exception(office365_client_error)
150
+ options = {
151
+ :extra => {
152
+ :status_code => office365_client_error.status_code,
153
+ :endpoint => office365_client_error.endpoint
154
+ }
155
+ }
156
+ ConnectorsShared::ExceptionTracking.capture_exception(office365_client_error, options)
157
+ end
158
+
159
+ def yield_drive_items(drive_id, drive_owner_name:, drive_name:, break_after_page: false, &block)
160
+ client.list_items(drive_id, break_after_page: break_after_page) do |item|
161
+ yield_single_document_change(:identifier => "Office365 change: #{item&.id} (#{Office365::Adapter::GraphItem.get_path(item)})") do
162
+ item.drive_owner_name = drive_owner_name
163
+ item.drive_name = drive_name
164
+ yield_create_or_update(drive_id, item, &block)
165
+ end
166
+ end
167
+ end
168
+
169
+ def yield_correct_actions_and_converted_item(drive_id, item, &block)
170
+ if item.deleted.nil?
171
+ yield_create_or_update(drive_id, item, &block)
172
+ else
173
+ yield :delete, convert_id_to_fp_id(item.id)
174
+ end
175
+ end
176
+
177
+ def yield_changes(drive_id, drive_owner_name:, drive_name:, start_delta_link: nil, last_modified: nil, break_after_page: false, &block)
178
+ client.list_changes(:drive_id => drive_id, :start_delta_link => start_delta_link, :last_modified => last_modified, :break_after_page => break_after_page) do |item|
179
+ yield_single_document_change(:identifier => "Office365 change: #{item&.id} (#{Office365::Adapter::GraphItem.get_path(item)})") do
180
+ item.drive_owner_name = drive_owner_name
181
+ item.drive_name = drive_name
182
+ yield_correct_actions_and_converted_item(drive_id, item, &block)
183
+ end
184
+ end
185
+ end
186
+
187
+ def yield_create_or_update(drive_id, item)
188
+ item = with_permissions(drive_id, item)
189
+
190
+ document = generate_document(item)
191
+ download_args =
192
+ if downloadable?(item)
193
+ download_args_and_proc(
194
+ id: document.fetch(:id),
195
+ name: item.name,
196
+ size: item[:size],
197
+ download_args: { :download_url => item.fetch('@microsoft.graph.downloadUrl') }
198
+ ) do |args|
199
+ download(args)
200
+ end
201
+ else
202
+ []
203
+ end
204
+ yield :create_or_update, document, download_args
205
+ end
206
+
207
+ def downloadable?(item)
208
+ item.key?('@microsoft.graph.downloadUrl')
209
+ end
210
+
211
+ def generate_document(item)
212
+ if item.file
213
+ adapter.swiftype_document_from_file(item)
214
+ elsif item.folder
215
+ adapter.swiftype_document_from_folder(item)
216
+ elsif item.package
217
+ adapter.swiftype_document_from_package(item)
218
+ else
219
+ raise "Unexpected Office 365 item type for item #{item}"
220
+ end
221
+ end
222
+
223
+ def with_permissions(drive_id, item)
224
+ item = item.dup
225
+ item.permissions = client.item_permissions(drive_id, item.id) if config.index_permissions
226
+ item
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,47 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'connectors_sdk/office365/adapter'
10
+
11
+ module ConnectorsSdk
12
+ module SharePoint
13
+ class Adapter < Office365::Adapter
14
+ generate_id_helpers :share_point, 'share_point'
15
+
16
+ def self.swiftype_document_from_file(file)
17
+ FileGraphItem.new(file).to_swiftype_document
18
+ end
19
+
20
+ def self.swiftype_document_from_folder(folder)
21
+ FolderGraphItem.new(folder).to_swiftype_document
22
+ end
23
+
24
+ def self.swiftype_document_from_package(package)
25
+ PackageGraphItem.new(package).to_swiftype_document
26
+ end
27
+
28
+ class FileGraphItem < Office365::Adapter::FileGraphItem
29
+ def self.convert_id_to_fp_id(id)
30
+ ConnectorsSdk::SharePoint::Adapter.share_point_id_to_fp_id(id)
31
+ end
32
+ end
33
+
34
+ class FolderGraphItem < Office365::Adapter::FolderGraphItem
35
+ def self.convert_id_to_fp_id(id)
36
+ ConnectorsSdk::SharePoint::Adapter.share_point_id_to_fp_id(id)
37
+ end
38
+ end
39
+
40
+ class PackageGraphItem < Office365::Adapter::PackageGraphItem
41
+ def self.convert_id_to_fp_id(id)
42
+ ConnectorsSdk::SharePoint::Adapter.share_point_id_to_fp_id(id)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,91 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'connectors_shared'
10
+ require 'signet'
11
+ require 'signet/oauth_2'
12
+ require 'signet/oauth_2/client'
13
+
14
+ module ConnectorsSdk
15
+ module SharePoint
16
+ class Authorization
17
+ class << self
18
+ def authorization_url
19
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
20
+ end
21
+
22
+ def token_credential_uri
23
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
24
+ end
25
+
26
+ def authorization_uri(params)
27
+ missing = missing_fields(params, %w[client_id])
28
+ unless missing.blank?
29
+ raise ConnectorsShared::ClientError.new("Missing required fields: #{missing.join(', ')}")
30
+ end
31
+
32
+ params[:response_type] = 'code'
33
+ params[:additional_parameters] = { :prompt => 'consent' }
34
+ client = oauth_client(params)
35
+ client.authorization_uri.to_s
36
+ end
37
+
38
+ def access_token(params)
39
+ missing = missing_fields(params, %w[client_id client_secret code redirect_uri])
40
+ unless missing.blank?
41
+ raise ConnectorsShared::ClientError.new("Missing required fields: #{missing.join(', ')}")
42
+ end
43
+
44
+ params[:grant_type] = 'authorization_code'
45
+ client = oauth_client(params)
46
+ client.fetch_access_token
47
+ end
48
+
49
+ def refresh(params)
50
+ missing = missing_fields(params, %w[client_id client_secret refresh_token])
51
+ unless missing.blank?
52
+ raise ConnectorsShared::ClientError.new("Missing required fields: #{missing.join(', ')}")
53
+ end
54
+
55
+ params[:grant_type] = 'refresh_token'
56
+ client = oauth_client(params)
57
+ client.refresh!
58
+ rescue StandardError => e
59
+ ConnectorsShared::ExceptionTracking.log_exception(e)
60
+ raise ConnectorsShared::TokenRefreshFailedError
61
+ end
62
+
63
+ def oauth_client(params)
64
+ options = params.merge(
65
+ :authorization_uri => authorization_url,
66
+ :token_credential_uri => token_credential_uri,
67
+ :scope => oauth_scope
68
+ )
69
+ options[:state] = JSON.dump(options[:state]) if options[:state]
70
+ Signet::OAuth2::Client.new(options)
71
+ end
72
+
73
+ def oauth_scope
74
+ %w[
75
+ User.ReadBasic.All
76
+ Group.Read.All
77
+ Directory.AccessAsUser.All
78
+ Files.Read
79
+ Files.Read.All
80
+ Sites.Read.All
81
+ offline_access
82
+ ]
83
+ end
84
+
85
+ def missing_fields(params, required = [])
86
+ Array.wrap(required).select { |field| params[field.to_sym].nil? }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,31 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'connectors_sdk/office365/extractor'
10
+ require 'connectors_sdk/share_point/adapter'
11
+
12
+ module ConnectorsSdk
13
+ module SharePoint
14
+ class Extractor < ConnectorsSdk::Office365::Extractor
15
+
16
+ private
17
+
18
+ def convert_id_to_fp_id(id)
19
+ ConnectorsSdk::SharePoint::Adapter.share_point_id_to_fp_id(id)
20
+ end
21
+
22
+ def adapter
23
+ ConnectorsSdk::SharePoint::Adapter
24
+ end
25
+
26
+ def drives
27
+ client.share_point_drives(:fields => %w(id owner name driveType)).sort_by(&:id)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'connectors_sdk/office365/config'
10
+ require 'connectors_sdk/share_point/extractor'
11
+ require 'connectors_sdk/share_point/authorization'
12
+ require 'bson'
13
+
14
+ module ConnectorsSdk
15
+ module SharePoint
16
+ SERVICE_TYPE = 'share_point'
17
+
18
+ class HttpCallWrapper
19
+ def extractor(params)
20
+ cursors = params.fetch(:cursors, {}) || {}
21
+ features = params.fetch(:features, {}) || {}
22
+
23
+ # XXX can we cache that class across calls?
24
+ ConnectorsSdk::SharePoint::Extractor.new(
25
+ content_source_id: BSON::ObjectId.new,
26
+ service_type: SERVICE_TYPE,
27
+ authorization_data_proc: proc { { access_token: params[:access_token] } },
28
+ client_proc: proc { ConnectorsSdk::Office365::CustomClient.new(:access_token => params[:access_token], :cursors => cursors) },
29
+ config: ConnectorsSdk::Office365::Config.new(:cursors => cursors, :drive_ids => 'all', :index_permissions => params[:index_permissions] || false),
30
+ features: features
31
+ )
32
+ end
33
+
34
+ def document_batch(params)
35
+ results = []
36
+
37
+ @extractor = extractor(params)
38
+
39
+ @extractor.yield_document_changes(:break_after_page => true, :modified_since => @extractor.config.cursors['modified_since']) do |action, doc, download_args_and_proc|
40
+ download_obj = nil
41
+ if download_args_and_proc
42
+ download_obj = {
43
+ id: download_args_and_proc[0],
44
+ name: download_args_and_proc[1],
45
+ size: download_args_and_proc[2],
46
+ download_args: download_args_and_proc[3]
47
+ }
48
+ end
49
+
50
+ results << {
51
+ :action => action,
52
+ :document => doc,
53
+ :download => download_obj
54
+ }
55
+ end
56
+
57
+ results
58
+ rescue ConnectorsSdk::Office365::CustomClient::ClientError => e
59
+ raise e.status_code == 401 ? ConnectorsShared::InvalidTokenError : e
60
+ end
61
+
62
+ def cursors
63
+ @extractor.config.cursors
64
+ end
65
+
66
+ def completed?
67
+ @extractor.completed
68
+ end
69
+
70
+ def deleted(params)
71
+ results = []
72
+ extractor(params).yield_deleted_ids(params[:ids]) do |id|
73
+ results << id
74
+ end
75
+ results
76
+ rescue ConnectorsSdk::Office365::CustomClient::ClientError => e
77
+ raise e.status_code == 401 ? ConnectorsShared::InvalidTokenError : e
78
+ end
79
+
80
+ def permissions(params)
81
+ extractor(params).yield_permissions(params[:user_id]) do |permissions|
82
+ return permissions
83
+ end
84
+ rescue ConnectorsSdk::Office365::CustomClient::ClientError => e
85
+ raise e.status_code == 401 ? ConnectorsShared::InvalidTokenError : e
86
+ end
87
+
88
+ def authorization_uri(body)
89
+ ConnectorsSdk::SharePoint::Authorization.authorization_uri(body)
90
+ end
91
+
92
+ def access_token(params)
93
+ ConnectorsSdk::SharePoint::Authorization.access_token(params)
94
+ end
95
+
96
+ def refresh(params)
97
+ ConnectorsSdk::SharePoint::Authorization.refresh(params)
98
+ end
99
+
100
+ def download(params)
101
+ extractor(params).download(params[:meta])
102
+ end
103
+
104
+ def name
105
+ 'SharePoint'
106
+ end
107
+
108
+ def source_status(params)
109
+ client = ConnectorsSdk::Office365::CustomClient.new(:access_token => params[:access_token])
110
+ client.me
111
+ { :status => 'OK', :statusCode => 200, :message => 'Connected to SharePoint' }
112
+ rescue StandardError => e
113
+ { :status => 'FAILURE', :statusCode => e.is_a?(ConnectorsSdk::Office365::CustomClient::ClientError) ? e.status_code : 500, :message => e.message }
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,16 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ require 'connectors_shared'
8
+
9
+ def required_path(absolute_path)
10
+ absolute_dir = File.dirname(absolute_path)
11
+ relative_dir = absolute_dir.sub(/.*lib\/connectors_sdk/, 'connectors_sdk')
12
+ name = File.basename(absolute_path, '.rb')
13
+ File.join(relative_dir, name)
14
+ end
15
+
16
+ Dir[File.join(__dir__, 'connectors_sdk/**/*.rb')].each { |f| require required_path(f) }
@@ -0,0 +1,14 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ module ConnectorsShared
8
+ class Constants
9
+ THUMBNAIL_FIELDS = %w[_thumbnail_80x100 _thumbnail_310x430].freeze
10
+ SUBEXTRACTOR_RESERVED_FIELDS = %w[_subextracted_as_of _subextracted_version].freeze
11
+ ALLOW_FIELD = '_allow_permissions'.freeze
12
+ DENY_FIELD = '_deny_permissions'.freeze
13
+ end
14
+ end
@@ -0,0 +1,126 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ require 'active_support/core_ext/string'
8
+
9
+ module ConnectorsShared
10
+ class DocumentError
11
+ attr_accessor :error_class, :error_message, :stack_trace, :error_id
12
+
13
+ def initialize(error_class, error_message, stack_trace, error_id)
14
+ @error_class = error_class
15
+ @error_message = error_message
16
+ @error_id = error_id
17
+
18
+ # keywords must be < 32kb, UTF-8 chars can be up to 3 bytes, thus 32k/3 ~= 10k
19
+ # See https://github.com/elastic/workplace-search-team/issues/1723
20
+ @stack_trace = stack_trace.truncate(10_000)
21
+ end
22
+
23
+ def to_h
24
+ {
25
+ 'error_class' => error_class,
26
+ 'error_message' => error_message,
27
+ 'stack_trace' => stack_trace,
28
+ 'error_id' => error_id
29
+ }
30
+ end
31
+ end
32
+
33
+ class ClientError < StandardError; end
34
+ class EvictionWithNoProgressError < StandardError; end
35
+ class EvictionError < StandardError
36
+ attr_accessor :cursors
37
+
38
+ def initialize(message = nil, cursors: nil)
39
+ super(message)
40
+ @cursors = cursors
41
+ end
42
+ end
43
+
44
+ class SuspendedJobError < StandardError
45
+ attr_accessor :suspend_until, :cursors
46
+
47
+ def initialize(message = nil, suspend_until:, cursors: nil)
48
+ super(message)
49
+ @suspend_until = suspend_until
50
+ @cursors = cursors
51
+ end
52
+ end
53
+ class ThrottlingError < SuspendedJobError; end
54
+ class TransientServerError < SuspendedJobError; end
55
+ class UnrecoverableServerError < StandardError; end
56
+ class TransientSubextractorError < StandardError; end
57
+ class JobDocumentLimitError < StandardError; end
58
+ class JobClaimingError < StandardError; end
59
+
60
+ class MonitoringError < StandardError
61
+ attr_accessor :tripped_by
62
+
63
+ def initialize(message = nil, tripped_by: nil)
64
+ super("#{message}#{tripped_by.present? ? " Tripped by - #{tripped_by.class}: #{tripped_by.message}" : ''}")
65
+ @tripped_by = tripped_by
66
+ end
67
+ end
68
+ class MaxSuccessiveErrorsExceededError < MonitoringError; end
69
+ class MaxErrorsExceededError < MonitoringError; end
70
+ class MaxErrorsInWindowExceededError < MonitoringError; end
71
+
72
+ class JobSyncNotPossibleYetError < StandardError
73
+ attr_accessor :sync_will_be_possible_at
74
+
75
+ def initialize(message = nil, sync_will_be_possible_at: nil)
76
+ human_readable_errors = []
77
+
78
+ human_readable_errors.push(message) unless message.nil?
79
+ human_readable_errors.push("Content source was created too recently to schedule jobs, next job scheduling is possible at #{sync_will_be_possible_at}.") unless sync_will_be_possible_at.nil?
80
+
81
+ super(human_readable_errors.join(' '))
82
+ end
83
+ end
84
+ class PlatinumLicenseRequiredError < StandardError; end
85
+ class JobInterruptedError < StandardError; end
86
+ class JobCannotBeUpdatedError < StandardError; end
87
+ class SecretInvalidError < StandardError; end
88
+ class InvalidIndexingConfigurationError < StandardError; end
89
+ class InvalidTokenError < StandardError; end
90
+ class TokenRefreshFailedError < StandardError; end
91
+ class ConnectorNotAvailableError < StandardError; end
92
+
93
+ # For when we want to explicitly set a #cause but can't
94
+ class ExplicitlyCausedError < StandardError
95
+ attr_reader :reason
96
+
97
+ def initialize(reason)
98
+ @reason = reason
99
+ end
100
+ end
101
+
102
+ class PublishingFailedError < ExplicitlyCausedError; end
103
+
104
+ class Error
105
+ attr_reader :status_code, :code, :message
106
+
107
+ def initialize(status_code, code, message)
108
+ @status_code = status_code
109
+ @code = code
110
+ @message = message
111
+ end
112
+
113
+ def to_h
114
+ {
115
+ 'code' => @code,
116
+ 'message' => @message
117
+ }
118
+ end
119
+ end
120
+
121
+ INTERNAL_SERVER_ERROR = ConnectorsShared::Error.new(500, 'INTERNAL_SERVER_ERROR', 'Internal server error')
122
+ INVALID_API_KEY = ConnectorsShared::Error.new(401, 'INVALID_API_KEY', 'Invalid API key')
123
+ UNSUPPORTED_AUTH_SCHEME = ConnectorsShared::Error.new(401, 'UNSUPPORTED_AUTH_SCHEME', 'Unsupported authorization scheme')
124
+ INVALID_ACCESS_TOKEN = ConnectorsShared::Error.new(401, 'INVALID_ACCESS_TOKEN', 'Invalid/expired access token, please refresh the token')
125
+ TOKEN_REFRESH_ERROR = ConnectorsShared::Error.new(401, 'TOKEN_REFRESH_ERROR', 'Failed to refresh token, please re-authenticate the application')
126
+ end