connectors_sdk 8.3.0.0.pre.20220414T060419Z

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.
@@ -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?
55
+ break
56
+ end
57
+ end
58
+
59
+ if break_after_page && config.cursors['page_cursor'].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